1. 引言
JVM 垃圾回收(Garbage Collection,简称 GC)是 Java 面试中几乎必考的核心知识点。无论是校招还是社招,面试官都喜欢通过 GC 相关的问题来考察候选人对 JVM 内存管理机制的理解深度。本文整理了 JVM 垃圾回收的高频面试题,从基础概念到进阶原理,帮助你在面试中从容应对。
2. 什么是垃圾回收
垃圾回收(GC)是 JVM 自动管理内存的一种机制。它负责识别并回收堆内存中不再被引用的对象,释放内存空间,避免内存泄漏和内存溢出。
在 Java 中,程序员不需要像 C/C++ 那样手动释放内存,JVM 的垃圾回收器会自动完成这一工作。但这并不意味着我们可以完全忽视内存管理,理解 GC 的工作原理对于写出高性能、低延迟的 Java 应用至关重要。
3. 如何判断对象是否可以被回收
3.1 引用计数法
引用计数法是为每个对象维护一个引用计数器,当对象被引用时计数器加 1,引用失效时计数器减 1。当计数器为 0 时,对象可以被回收。
缺点:无法解决循环引用问题。例如 A 引用 B,B 引用 A,但两者都不再被外部引用时,它们的计数器都不为 0,导致无法被回收。
3.2 可达性分析算法
这是目前主流 JVM(如 HotSpot)采用的算法。它从一组称为 GC Roots 的根对象出发,通过引用链向下搜索,能够到达的对象标记为存活对象,无法到达的对象判定为可回收对象。
GC Roots 包括:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中 JNI 引用的对象
- 活跃线程(Thread)对象
4. 垃圾回收算法有哪些
4.1 标记-清除算法(Mark-Sweep)
原理:先标记所有需要回收的对象,然后统一回收被标记的对象。
优点:实现简单,不需要移动对象。
缺点:
- 标记和清除效率都不高
- 会产生大量不连续的内存碎片,导致后续大对象分配困难
4.2 复制算法(Copying)
原理:将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当一块内存用完后,将存活对象复制到另一块,然后清理已使用过的空间。
优点:实现简单,运行高效,不会产生内存碎片。
缺点:内存利用率只有一半,存在空间浪费。
应用场景:HotSpot 虚拟机的新生代采用此算法,将新生代划分为 Eden 区和两个 Survivor 区(默认比例为 8:1:1)。
4.3 标记-整理算法(Mark-Compact)
原理:先标记所有需要回收的对象,然后让所有存活对象向一端移动,最后直接清理掉端边界以外的内存。
优点:不会产生内存碎片,内存利用率高。
缺点:移动对象需要更新引用,效率比复制算法低。
应用场景:老年代通常采用此算法。
4.4 分代收集算法
原理:根据对象存活周期的不同,将堆内存划分为新生代和老年代。新生代对象存活率低,采用复制算法;老年代对象存活率高,采用标记-清除或标记-整理算法。
这是目前主流 JVM 垃圾收集器普遍采用的策略。
5. 什么是分代回收
分代回收是 HotSpot 虚拟机采用的核心策略,它将堆内存划分为以下几个区域:
- 新生代(Young Generation):存放生命周期短的对象,包括 Eden 区、Survivor 0 区(S0)和 Survivor 1 区(S1)。
- 老年代(Old Generation):存放生命周期长或经过多次 Minor GC 后仍然存活的对象。
- 元空间(Metaspace):JDK 8 之后取代永久代,存放类的元数据信息。
新生代 GC(Minor GC):发生在新生代的垃圾回收,频率高、速度快。
老年代 GC(Major GC / Full GC):发生在老年代的垃圾回收,频率低、速度慢,通常伴随至少一次 Minor GC。
6. 常见的垃圾收集器有哪些
6.1 Serial 收集器
单线程收集器,进行垃圾回收时必须暂停所有工作线程(Stop The World)。适用于单 CPU 环境下的客户端模式,简单高效。
6.2 ParNew 收集器
Serial 收集器的多线程版本,使用多条线程并行进行垃圾回收。是服务端模式下首选的新生代收集器之一。
6.3 Parallel Scavenge 收集器
关注点是吞吐量(高效率利用 CPU),适合后台运算而不需要太多交互的任务。JDK 8 默认的新生代收集器。
6.4 Serial Old 收集器
Serial 收集器的老年代版本,单线程,采用标记-整理算法。
6.5 Parallel Old 收集器
Parallel Scavenge 收集器的老年代版本,多线程,采用标记-整理算法。
6.6 CMS 收集器
以获取最短回收停顿时间为目标的收集器,基于标记-清除算法。适合互联网站等对响应时间要求较高的场景。
缺点:
- 对 CPU 资源敏感
- 无法处理浮动垃圾
- 会产生内存碎片
6.7 G1 收集器
面向服务端应用的垃圾收集器,将堆划分为多个大小相等的 Region,可以预测停顿时间。JDK 9 之后成为默认收集器。
特点:
- 并行与并发
- 分代收集
- 空间整合
- 可预测的停顿时间
6.8 ZGC 收集器
JDK 11 引入的实验性收集器,JDK 15 转正。基于 Region 内存布局,使用染色指针和读屏障技术,停顿时间极短(毫秒级),适用于大堆内存场景。
7. 什么是 Stop The World
Stop The World(STW)是指垃圾回收过程中,JVM 需要暂停所有用户线程,只让垃圾回收线程工作。这是导致 GC 停顿的根本原因。
影响:STW 时间越长,应用的响应延迟越高。因此,现代垃圾收集器的核心优化目标就是缩短 STW 时间。
优化手段:
- 使用并发收集器(如 CMS、G1)让部分回收工作与用户线程并发执行
- 使用低停顿收集器(如 ZGC、Shenandoah)
8. 什么是 Minor GC 和 Full GC
Minor GC:发生在新生代的垃圾回收。由于新生代对象大多朝生夕灭,Minor GC 非常频繁,回收速度也很快。
触发条件:Eden 区空间不足时触发。
Full GC:对整个堆(新生代 + 老年代 + 元空间)进行垃圾回收。Full GC 速度慢、停顿时间长,应尽量避免。
触发条件:
- 老年代空间不足
- 元空间空间不足
- 调用 System.gc()(只是建议,不一定会执行)
- CMS 收集器出现 Concurrent Mode Failure
9. 对象什么时候进入老年代
- 对象年龄达到阈值 :对象每经历一次 Minor GC 仍然存活,年龄加 1,默认达到 15 岁时进入老年代(可通过
-XX:MaxTenuringThreshold设置)。 - 大对象直接进入老年代 :超过
-XX:PretenureSizeThreshold设置的大对象直接分配在老年代,避免在新生代频繁复制。 - 动态年龄判定:Survivor 区中相同年龄所有对象大小总和超过 Survivor 区一半时,年龄大于等于该值的对象直接进入老年代。
- 空间分配担保:Minor GC 后存活对象过多,Survivor 区无法容纳时,多余对象进入老年代。
10. 如何排查内存泄漏和内存溢出
10.1 常见排查工具
- jps:查看 Java 进程
- jstat:查看 JVM 统计信息
- jmap:导出堆转储文件
- jstack:查看线程堆栈
- MAT(Memory Analyzer Tool):分析堆转储文件
- VisualVM:可视化监控 JVM
10.2 排查步骤
- 使用
jps找到目标 Java 进程 - 使用
jmap -dump:format=b,file=heap.hprof <pid>导出堆转储文件 - 使用 MAT 或 VisualVM 分析堆转储文件,定位大对象和可疑引用链
- 结合代码分析,找到内存泄漏的根源
10.3 常见内存泄漏场景
- 静态集合类持有对象引用未释放
- 未关闭的连接(数据库连接、IO 流)
- 监听器未注销
- 缓存中的对象未清理
- ThreadLocal 使用不当
11. 高频面试题汇总
11.1 基础题
Q1:Java 中如何判断对象可以被回收?
答:主要通过可达性分析算法,从 GC Roots 出发,无法到达的对象可以被回收。引用计数法因无法解决循环引用问题,主流 JVM 已不采用。
Q2:GC Roots 包含哪些?
答:虚拟机栈中引用的对象、方法区静态属性和常量引用的对象、本地方法栈 JNI 引用的对象、活跃线程对象。
Q3:新生代为什么采用复制算法?
答:新生代对象存活率低,复制算法效率高且不会产生内存碎片。通过 Eden 和两个 Survivor 区的划分,将内存利用率提升到 90% 左右。
11.2 进阶题
Q4:CMS 和 G1 的区别是什么?
答:CMS 基于标记-清除算法,会产生内存碎片;G1 基于 Region 划分,采用标记-整理算法,空间整合更好。G1 可以预测停顿时间,适合大堆场景。
Q5:什么是 Concurrent Mode Failure?
答:CMS 收集器在并发清理阶段,用户线程仍在运行,如果此时老年代空间不足,会触发 Concurrent Mode Failure,导致退化为 Serial Old 收集器进行 Full GC,停顿时间大幅增加。
Q6:如何减少 Full GC 的频率?
答:合理设置堆大小和新生代比例、避免创建过多大对象、及时释放无用引用、使用合适的垃圾收集器、优化代码减少对象创建。
11.3 场景题
Q7:线上应用频繁 Full GC,如何排查?
答:先通过 jstat -gcutil 查看 GC 频率和堆使用情况,再通过 jmap 导出堆转储,用 MAT 分析大对象和引用链,定位内存泄漏点,最后结合代码优化。
Q8:如何选择垃圾收集器?
答:根据应用场景选择。追求吞吐量选 Parallel Scavenge + Parallel Old;追求低延迟选 G1 或 ZGC;单机小应用选 Serial。JDK 8 默认 Parallel,JDK 9+ 默认 G1。
12. 总结
JVM 垃圾回收是 Java 面试的核心考点,需要掌握的知识点包括:
- 对象存活判定算法(可达性分析)
- 三种基本回收算法(标记-清除、复制、标记-整理)
- 分代收集策略
- 主流垃圾收集器(Serial、Parallel、CMS、G1、ZGC)
- STW 机制及优化
- 内存泄漏排查方法
建议在理解原理的基础上,结合线上调优实践加深记忆。面试时不仅要能回答概念,更要能结合实际场景分析问题。