JVM 面试题
2026/1/15大约 5 分钟
JVM 面试题
内存模型
1. JVM 内存结构是怎样的?
- 程序计数器:记录当前线程执行的字节码行号,线程私有
- 虚拟机栈:存储栈帧(局部变量表、操作数栈等),线程私有
- 本地方法栈:为 Native 方法服务,线程私有
- 堆:存储对象实例,线程共享,GC 主要区域
- 方法区:存储类信息、常量、静态变量,线程共享
2. 堆和栈的区别?
| 特性 | 堆 | 栈 |
|---|---|---|
| 存储内容 | 对象实例 | 局部变量、方法调用 |
| 线程共享 | 共享 | 私有 |
| 生命周期 | GC 管理 | 方法结束自动释放 |
| 大小 | 较大 | 较小 |
| 异常 | OutOfMemoryError | StackOverflowError |
3. 什么是 TLAB?
Thread Local Allocation Buffer,线程本地分配缓冲区。
- 每个线程在堆中预先分配一块内存
- 对象优先在 TLAB 中分配
- 避免多线程竞争,提高分配效率
4. 对象的内存布局?
- 对象头:Mark Word + 类型指针
- 实例数据:字段内容
- 对齐填充:保证 8 字节对齐
5. 对象的访问定位方式?
- 句柄访问:栈 → 句柄池 → 对象
- 直接指针:栈 → 对象(HotSpot 使用)
垃圾回收
6. 如何判断对象可以被回收?
可达性分析:从 GC Roots 出发,不可达的对象可回收。
GC Roots 包括:
- 虚拟机栈中引用的对象
- 方法区中静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中 JNI 引用的对象
7. 四种引用类型?
| 类型 | 回收时机 | 用途 |
|---|---|---|
| 强引用 | 不会被回收 | 普通引用 |
| 软引用 | 内存不足时 | 缓存 |
| 弱引用 | GC 时 | WeakHashMap |
| 虚引用 | 随时 | 跟踪对象回收 |
8. 垃圾回收算法有哪些?
- 标记-清除:标记可回收对象,直接清除。缺点:内存碎片
- 标记-复制:将存活对象复制到另一块内存。缺点:内存利用率低
- 标记-整理:将存活对象移动到一端。缺点:效率较低
- 分代收集:新生代用复制算法,老年代用标记-清除/整理
9. 新生代为什么用复制算法?
- 新生代对象存活率低(约 2%)
- 复制算法效率高,无碎片
- Eden:S0:S1 = 8:1:1,内存利用率 90%
10. 常见的垃圾收集器?
| 收集器 | 特点 | 适用场景 |
|---|---|---|
| Serial | 单线程 | 小内存 |
| ParNew | Serial 多线程版 | 配合 CMS |
| Parallel Scavenge | 关注吞吐量 | 后台任务 |
| CMS | 低停顿 | Web 应用 |
| G1 | 可预测停顿 | 大内存 |
| ZGC | 超低延迟 | 超大内存 |
11. CMS 收集器的工作过程?
- 初始标记(STW):标记 GC Roots 直接关联的对象
- 并发标记:遍历对象图
- 重新标记(STW):修正并发标记期间的变动
- 并发清除:清除垃圾对象
12. G1 收集器的特点?
- Region 化内存管理
- 可预测的停顿时间模型
- 混合回收(新生代 + 部分老年代)
- 适合大内存、多核 CPU
13. Minor GC 和 Full GC 的区别?
| 类型 | 触发条件 | 回收区域 | 停顿时间 |
|---|---|---|---|
| Minor GC | Eden 满 | 新生代 | 短 |
| Full GC | 老年代满/方法区满 | 整个堆 | 长 |
14. 对象什么时候进入老年代?
- 年龄达到阈值(默认 15)
- 大对象直接分配
- Survivor 空间不足
- 动态年龄判断
类加载
15. 类加载的过程?
- 加载:读取字节码,生成 Class 对象
- 验证:确保字节码符合规范
- 准备:为静态变量分配内存,设置初始值
- 解析:符号引用转直接引用
- 初始化:执行
<clinit>()方法
16. 什么是双亲委派模型?
类加载器收到加载请求时,先委派给父加载器,父加载器无法加载时才自己加载。
好处:
- 避免重复加载
- 保证核心类安全
17. 如何打破双亲委派?
- 重写
loadClass()方法 - 使用线程上下文类加载器(SPI)
- OSGi 模块化
18. 类加载器有哪些?
- Bootstrap ClassLoader:加载 JAVA_HOME/lib
- Extension ClassLoader:加载 JAVA_HOME/lib/ext
- Application ClassLoader:加载 classpath
性能调优
19. 常用的 JVM 参数?
# 堆内存
-Xms4g -Xmx4g # 初始和最大堆大小
-Xmn2g # 新生代大小
# 元空间
-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=256m
# GC
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
# 日志
-Xlog:gc*:file=gc.log20. 如何排查 OOM?
- 添加参数
-XX:+HeapDumpOnOutOfMemoryError - 使用 MAT 分析 dump 文件
- 查找大对象和内存泄漏
21. 如何排查 CPU 100%?
# 1. 找到 CPU 高的进程
top
# 2. 找到 CPU 高的线程
top -Hp <pid>
# 3. 转换线程 ID 为十六进制
printf "%x\n" <tid>
# 4. 查看线程堆栈
jstack <pid> | grep <tid_hex> -A 3022. 常用的 JVM 工具?
| 工具 | 用途 |
|---|---|
| jps | 查看 Java 进程 |
| jstat | 查看 GC 统计 |
| jmap | 生成堆 dump |
| jstack | 查看线程堆栈 |
| jinfo | 查看 JVM 参数 |
| jconsole | 图形化监控 |
| VisualVM | 综合分析工具 |
23. 什么情况会发生 StackOverflowError?
- 递归调用过深
- 方法调用链过长
- 栈帧过大
24. 什么情况会发生 OutOfMemoryError?
- Java heap space:堆内存不足
- Metaspace:元空间不足
- GC overhead limit exceeded:GC 时间过长
- Direct buffer memory:直接内存不足
- Unable to create new native thread:线程数过多
其他
25. JIT 编译器是什么?
Just-In-Time 编译器,将热点代码编译为本地机器码。
- C1 编译器:快速编译,简单优化
- C2 编译器:深度优化,编译较慢
26. 什么是逃逸分析?
分析对象的作用域,判断对象是否逃逸出方法或线程。
优化:
- 栈上分配:对象不逃逸,直接在栈上分配
- 标量替换:将对象拆解为基本类型
- 锁消除:对象不逃逸,消除同步锁
27. 什么是 Safe Point?
程序执行到特定位置时,可以安全地进行 GC。
Safe Point 位置:
- 方法调用
- 循环跳转
- 异常跳转
28. 什么是 STW?
Stop The World,GC 时暂停所有用户线程。
减少 STW 的方法:
- 使用并发收集器(CMS、G1、ZGC)
- 减少 Full GC 频率
- 合理设置堆大小