分代收集理论
候选人小周在面试阿里 P7 时,面试官问道:
"为什么 JVM 要分代?不分代不行吗?"
小周说:"分代可以提高 GC 效率..."面试官追问:"GC 的区域划分依据是什么?"
小周答不上来。面试官继续追问:"年轻代的对象引用了老年代,或者反过来,怎么办?"
小周彻底卡住了...
一、核心问题:分代收集理论 🔴
1.1 问题拆解
1.2 ❌ 错误示范
候选人原话 A:"分代是因为不同对象生命周期不同。"
问题诊断:这是表面原因。真正的理论基础是弱分代假说——大多数对象朝生夕死。分代收集基于这个观察,对不同存活率的对象使用不同算法。
候选人原话 B:"跨代引用不需要特别处理。"
问题诊断:跨代引用会导致 GC Roots 的扫描范围扩大。Minor GC 时需要扫描老年代,导致性能问题。
1.3 标准回答
P5 级别:两大假说
弱分代假说(Weak Generational Hypothesis):
大多数对象朝生夕死——大部分对象在创建后很快变得不可达,只有很少一部分对象会存活很长时间。
强分代假说(Strong Generational Hypothesis):
熬过多次 GC 的对象倾向于继续存活。
基于这两个假说的设计:
- 频繁死亡的对象应该被快速回收(年轻代)
- 长期存活的对象应该被缓慢回收(老年代)
- 不同区域使用不同的 GC 算法
对象年龄的计数:
对象每经历一次 Minor GC 并且存活(未被回收),年龄 age 增加 1。当 age 达到阈值(
-XX:MaxTenuringThreshold,默认 15)时,晋升到老年代。
P6 级别:跨代引用与卡表
跨代引用的问题:
当进行 Minor GC(只扫描年轻代)时,如果只扫描 GC Roots,需要扫描整个老年代来找到年轻代的引用——成本太高。
Card Table(卡表)解决方案:
JVM 使用卡表(Card Table)来解决跨代引用问题:
卡表的实现:
P7 级别:Remembered Set
Remembered Set(记忆集):
G1 和其他现代 GC 使用 Remembered Set 记录跨 Region 的引用:
Remembered Set 的作用是让 GC 追踪哪些 Region 中的对象引用了当前 Region,避免全堆扫描。
【面试官心理】 这道题我能问到 P7 级别,是因为分代收集理论是所有现代 GC 的基础。能说清卡表和 Remembered Set 的候选人说明他理解了 GC 优化的核心思想。
1.4 追问升级
追问:G1 的 Remembered Set 有什么性能问题?
G1 的 Remembered Set 需要维护每个 Region 的引用关系,在高引用变化率下(频繁的对象赋值)会产生大量 dirty cards,导致写屏障(Write Barrier)的开销增加。
二、生产避坑 🟡
2.1 跨代引用导致的 GC 变慢
频繁的对象赋值(跨代引用)会导致大量 dirty cards,增加 Minor GC 的成本。
2.2 参数配置
面试加分点:能说出"ZGC 的着色指针(Colored Pointers)技术将 GC 信息存储在指针上而非对象头上,使得 GC 状态读取不需要访问对象头,从而避免了读屏障的性能开销",说明他理解了现代 GC 的设计哲学。
面试陷阱:被问到"分代收集理论是谁提出的",很多人会说"不知道"。准确答案是:John McCarthy 在 1960 年的 Lisp GC 实现中首次引入了分代的概念。David Ungar 在 1984 年提出了generational garbage collection,奠定了现代分代 GC 的理论基础。