P45 面试重灾区:JVM 三色标记与 GC 核心架构
面试题:GC 的"三色标记"是什么?解决了什么问题?有什么缺陷?
1. 为什么需要三色标记
现代垃圾回收器(CMS、G1)用并发标记(GC 线程和应用线程同时跑)提高吞吐、降低停顿。并发标记期间对象引用会变化,普通的标记算法会漏标/错标,三色标记就是为了在并发标记中保证正确性。
2. 三色定义
| 颜色 | 含义 | 处理 |
|---|---|---|
| 白色 | 未被访问到的对象 | 标记结束后仍是白色 = 不可达,回收 |
| 灰色 | 自身被访问到,引用尚未全部扫描 | 中间状态,需要继续处理 |
| 黑色 | 自身和其引用都已扫描完 | 标记完成 |
标记过程:
text
① 初始:GC Roots 直接引用的对象标灰,其余全白
② 从灰色对象出发扫描其引用:引用的白色对象变灰,自己变黑
③ 重复直到没有灰色对象
④ 剩下的白色对象就是垃圾,回收3. 并发标记的致命问题:漏标
并发环境下,应用线程可能把"黑色对象"的引用改成指向"白色对象",同时灰色对象到该白色对象的引用被删掉:
text
黑色 A → 不再指向 C(C 还是白色)
灰色 B → C 的引用被 A 改走后,B 的引用也没了(实际上 C 被 A 引用着)
结果:C 是白色但可达 → 被误当垃圾回收 → 程序崩溃这个"黑色指向白色"的破坏需要同时满足两个条件:
- 黑色对象新增了指向白色对象的引用;
- 该白色对象的所有灰色引用被删除。
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)**拦截引用变更来保证不漏标、不错回收。