真实面经题目 · 原创解析

JVM 垃圾回收机制是什么?

JVM 垃圾回收机制的核心,是 JVM 自动识别已经不可达的对象并回收其占用的内存,从而降低手动释放内存的负担。面试回答要先明确:GC 主要发生在 Java 堆,尤其是新生代;同时也要补充方法区或元空间在类卸载、常量废弃时也可能被回收,但虚拟机栈、本地方法栈和程序计数器通常随线程或栈帧生命周期自动释放,不是 GC 的主要战场。

出现于:阿里巴巴 · 后端开发

60 秒回答模板

JVM 的垃圾回收是自动内存管理机制,主要解决堆内存中对象生命周期不可控的问题。对象创建后一般分配在 Java 堆,GC 会从一组 GC Roots 出发做可达性分析,比如线程栈中的引用、静态变量、JNI 引用等;凡是不能从这些根对象链路访问到的对象,就会被认为是不可达对象,后续可以被回收。实际 HotSpot JVM 通常按分代思想管理堆,原因是大部分对象生命周期很短,所以新对象优先进入新生代,Minor GC 会频繁回收 Eden 和 Survivor 区;如果对象存活多轮,或对象较大、空间不足,就可能晋升到老年代,老年代空间紧张时触发 Major GC 或 Full GC。GC 的主要发生区域是 Java 堆,尤其是新生代;方法区或元空间也可能发生类卸载和常量回收,但不是常规对象回收的主要区域。不同垃圾收集器在停顿时间、吞吐量、并发程度和内存整理策略上取舍不同,例如 G1 面向低停顿并按 region 回收,ZGC 更强调低延迟并把大量工作并发化。

考点 回收对象的判定机制
主线 GC 主要发生区域
易错点 只回答“垃圾回收发生在堆”但不补充新生代、老年代和元空…

深入解析

01

回收对象的判定机制

JVM 不会简单依赖引用计数来判断对象是否该回收,主流 HotSpot 使用的是可达性分析。GC 从 GC Roots 这组根对象出发,沿着引用链向下搜索,能被访问到的对象被认为仍然存活,访问不到的对象才进入可回收范围。这个机制能处理循环引用问题,因为两个对象即使互相引用,只要它们整体不再被 GC Roots 触达,仍然会被判定为不可达。需要注意,不可达不等于立刻释放,JVM 还要考虑对象的 finalize 历史、引用类型、具体收集器阶段和当前内存压力。

02

GC 主要发生区域

面试中问“垃圾回收主要在哪块区域发生”,标准回答应先说 Java 堆。堆是对象实例和数组的主要分配区域,也是对象数量最多、生命周期最复杂的区域,因此是 GC 的核心区域。更细一点说,堆内回收又主要集中在新生代,因为多数对象很快失效,回收新生代通常成本较低、收益较高。老年代也会回收,但频率通常低于新生代,代价也更高。方法区或元空间也存在回收,例如废弃常量、无用类卸载,但条件更苛刻,不能把它和堆对象回收混为一谈。虚拟机栈、程序计数器、本地方法栈通常随线程或方法调用自然释放。

03

分代回收流程

分代回收基于弱分代假说:大部分对象朝生夕死,少量对象会长期存活。对象一般先分配到 Eden 区,Eden 满时触发 Minor GC,存活对象被复制到 Survivor 区,并增加年龄;经历多次 Minor GC 仍存活的对象会晋升到老年代。这样做的好处是新生代可以用复制算法快速清理大量短命对象,而老年代使用标记-清除、标记-整理或区域化迁移等方式处理长寿对象。边界在于,如果分代比例、晋升阈值或分配速率不合适,会导致频繁晋升、老年代膨胀,最终引发更重的 GC 停顿。

04

常见收集算法

GC 的底层算法主要包括标记-清除、复制、标记-整理,以及在现代收集器中的组合变体。标记-清除先标记存活对象,再清理未标记对象,优点是思路简单,缺点是容易产生内存碎片。复制算法把存活对象复制到另一块空间,适合新生代,因为新生代存活对象少,复制成本可控。标记-整理会把存活对象向一端移动,解决碎片问题,但移动对象需要更新引用,暂停成本更高。现代收集器通常不是只用单一算法,而是按代、region、并发阶段和暂停目标组合使用。

05

垃圾收集器取舍

不同垃圾收集器解决的是同一个问题,但目标不同。Serial GC 简单,适合小堆或客户端场景;Parallel GC 更重视吞吐量,适合批处理;G1 把堆划分为多个 region,通过并发标记和优先回收垃圾比例高的区域来控制暂停时间;ZGC 则把大量标记、转移和重定位工作并发化,目标是低延迟。选择收集器不能只看名字,要结合堆大小、分配速率、响应时间 SLA、CPU 资源和对象存活特征。

06

STW 与性能影响

GC 不是完全无感的,很多阶段需要 Stop-The-World,也就是暂停业务线程,以保证对象图的一致性。Minor GC 通常暂停较短,但如果分配速率极高,也会造成频繁抖动;Full GC 通常代价更大,可能明显拉高接口延迟。并发收集器能减少停顿,但会消耗额外 CPU,并且应用线程继续运行时会产生新的引用变化,需要写屏障、读屏障、记忆集等机制维护正确性。因此调优本质是平衡吞吐量、延迟、内存占用和实现复杂度,而不是盲目追求“没有 GC”或“停顿为零”。

易错点

  • 只回答“垃圾回收发生在堆”但不补充新生代、老年代和元空间边界,答案会显得过浅。
  • 把引用计数当成 JVM 主流垃圾判定机制,忽略可达性分析和循环引用问题。
  • 认为不可达对象会立即释放,忽略 GC 触发时机、引用类型、finalize 历史和收集器阶段。
  • 把 Minor GC、Major GC、Full GC 说成绝对统一概念,不说明不同 JVM 和日志语境下可能存在差异。
  • 误以为栈内存也由 GC 频繁扫描回收,实际上栈帧通常随方法调用结束自动出栈释放。
  • 把垃圾收集算法和垃圾收集器混为一谈,例如把 G1 说成一种单纯的标记-清除算法。

面试官追问

为什么 JVM 不主要使用引用计数来做垃圾回收?

引用计数的优点是实现直观,对象引用数为零时就可以回收,但它很难处理循环引用。例如 A 引用 B,B 又引用 A,如果外部已经没有任何对象能访问它们,它们的引用计数仍然不为零。可达性分析从 GC Roots 出发,只关心对象是否能被根对象链路访问到,因此能正确识别这种互相引用但整体不可达的对象。

GC Roots 通常包括哪些对象?

GC Roots 可以理解为对象存活判定的起点,常见包括虚拟机栈中局部变量表引用的对象、方法区或类元数据中静态字段引用的对象、常量引用的对象、JNI 本地方法引用的对象,以及 JVM 内部一些正在使用的对象。只要某个对象能从这些根对象沿引用链访问到,就不能被当作垃圾回收。

Minor GC、Major GC 和 Full GC 有什么区别?

Minor GC 通常指新生代回收,发生频率高,目标是快速清理大量短命对象。Major GC 常被用来指老年代回收,但不同资料和日志语境中含义可能不完全一致。Full GC 一般表示对整个堆甚至关联的元空间等区域进行更完整的回收,停顿成本通常更高。面试中最好说明语义依赖具体 JVM 和日志上下文。

方法区或元空间会不会发生垃圾回收?

会,但它不是最主要的 GC 区域。方法区或元空间主要存放类元数据、运行时常量池等内容,回收重点通常是废弃常量和无用类。类卸载条件比较苛刻,例如该类的实例都已不可达、加载它的 ClassLoader 已不可达、对应 Class 对象也不可达,所以生产中元空间问题经常和类加载器泄漏有关。

为什么新生代适合使用复制算法?

新生代对象大多生命周期很短,每次 Minor GC 后真正存活的对象比例通常较低。复制算法只需要移动少量存活对象,然后整块清空原区域,速度快且不会产生碎片。它的代价是需要预留额外空间,并且如果存活对象突然增多,复制和晋升成本会上升,甚至给老年代带来压力。

G1 和 ZGC 的核心差异是什么?

G1 更强调在可控停顿和吞吐量之间取得平衡,它把堆拆成 region,通过并发标记识别回收收益高的区域,并在暂停时间目标内选择回收集合。ZGC 更强调极低延迟,把大量昂贵工作并发执行,减少业务线程长时间停顿。选择哪一个要看延迟要求、堆大小、CPU 余量和 JDK 版本。