Skip to content

P45 面试重灾区:JVM 三色标记与 GC 核心架构 ​

面试题:GC 的"三色标记"是什么?解决了什么问题?有什么缺陷?

1. 为什么需要三色标记 ​

现代垃圾回收器(CMS、G1)用并发标记(GC 线程和应用线程同时跑)提高吞吐、降低停顿。并发标记期间对象引用会变化,普通的标记算法会漏标/错标,三色标记就是为了在并发标记中保证正确性。

2. 三色定义 ​

颜色含义处理
白色未被访问到的对象标记结束后仍是白色 = 不可达,回收
灰色自身被访问到,引用尚未全部扫描中间状态,需要继续处理
黑色自身和其引用都已扫描完标记完成

标记过程:

text
① 初始:GC Roots 直接引用的对象标灰,其余全白
② 从灰色对象出发扫描其引用:引用的白色对象变灰,自己变黑
③ 重复直到没有灰色对象
④ 剩下的白色对象就是垃圾,回收

3. 并发标记的致命问题:漏标 ​

并发环境下,应用线程可能把"黑色对象"的引用改成指向"白色对象",同时灰色对象到该白色对象的引用被删掉:

text
黑色 A → 不再指向 C(C 还是白色)
灰色 B → C 的引用被 A 改走后,B 的引用也没了(实际上 C 被 A 引用着)
结果:C 是白色但可达 → 被误当垃圾回收 → 程序崩溃

这个"黑色指向白色"的破坏需要同时满足两个条件:

  1. 黑色对象新增了指向白色对象的引用;
  2. 该白色对象的所有灰色引用被删除。

4. 解决方案:写屏障 ​

  • 增量更新(Incremental Update):黑色对象新增引用时,把黑色对象重新标灰(CMS 使用);
  • 原始快照(SATB,Snapshot At The Beginning):记录灰色对象被删除的引用,保证"删除前快照"里的对象不丢(G1 使用)。

两者本质:拦截"引用变化"这个瞬间,防止白色对象被漏标。

5. 加分点 ​

  • 顺带讲 GC Roots:栈帧局部变量、静态变量、JNI 引用、常量池、活跃线程等;
  • 讲清楚 CMS(并发标记清除)的四个阶段:初始标记 → 并发标记 → 重新标记(增量更新)→ 并发清除;
  • G1 的 SATB + Remembered Set(RSet)解决跨 Region 引用;
  • 追问"为什么 G1 用 SATB、CMS 用增量更新":能说出设计取舍即可(SATB 更保守、避免浮动垃圾误回收,增量更新实现相对简单)。

一句话总结 ​

三色标记把对象分为白/灰/黑,让 GC 可以并发标记;并发下"黑→白新增引用 + 灰→白引用删除"会导致白对象漏标,靠**写屏障(CMS 增量更新 / G1 SATB)**拦截引用变更来保证不漏标、不错回收。

基于 VitePress 重建