三色标记算法通过白(未访问)、灰(已访问未扫完)、黑(已访问且全扫完)状态实现并发标记,核心是用写屏障(如SATB或增量更新)破坏漏标条件:G1用SATB记录引用删除并事后重扫,CMS用增量更新将新引黑白对象的黑对象重置为灰。

三色标记算法在 Java 垃圾回收(如 G1、ZGC、Shenandoah)中实现并发标记,核心是解决“用户线程与标记线程同时运行时对象引用关系可能被修改”导致的漏标问题。它不靠全局停顿,而是通过写屏障(Write Barrier)配合颜色标记 + 增量更新或原始快照(SATB)机制来保证准确性。
三色标记的基本颜色含义
每个对象在标记过程中被赋予一种颜色,表示其在可达性分析中的状态:
- 白色:尚未被标记,初始状态;若标记结束仍为白色,则视为不可达,可回收
- 灰色:自身已被标记,但其引用的子对象尚未全部扫描(在标记栈/队列中待处理)
- 黑色:自身及所有直接/间接引用的对象都已标记完成
并发场景下的核心问题:漏标
当用户线程修改引用(如 A.field = B,或 B = null)时,若标记线程已将 A 标为黑色,而 B 是新引用的对象或刚被断开的对象,就可能导致:
- B 从未被扫描 → 漏标(本该活,却被回收)
- 或 B 已被扫描后又被断开 → 无害,但 SATB 会记录它以避免误回收
因此,并发标记必须捕获这些“变动”,并兜底处理。
立即学习“Java免费学习笔记(深入)”;
两种主流解决方案:增量更新(IU) vs 原始快照(SATB)
现代 JVM 根据 GC 算法选择其中一种,均依赖写屏障在引用赋值前/后插入检查逻辑:
-
增量更新(Incremental Update)——用于 CMS、G1 的部分阶段:
在 引用被写入时(set field),若被赋值对象(如 A)已是黑色,而新引用对象(如 B)是白色,则将 A 重新标记为灰色(“推回队列”),确保 B 后续被扫描。本质是“补救已错过的引用”。 -
原始快照(Snapshot-At-The-Beginning, SATB)——G1 默认、ZGC/Shenandoah 的基础:
在 引用被覆盖前(old value 被丢弃前),用写屏障记录下即将消失的引用(如 A.field 原来指向 C,现在要改成 B,则把 C 记录进 SATB 缓冲区)。标记阶段结束后,再扫描这些“曾存在过但可能已断开”的对象,确保它们不被误回收(即使 C 实际已不可达,SATB 也会保守保留)。
实际执行流程(以 G1 SATB 并发标记为例)
整个过程无需 STW(除了初始快照和最终修正的极短暂停):
- 初始标记(Initial Mark):STW,仅标记 GC Roots 直接关联对象(快)
- 并发标记(Concurrent Marking):用户线程运行,标记线程遍历对象图;写屏障实时记录 SATB 日志
- 最终标记(Remark):STW,处理 SATB 缓冲区、更新卡片表、修正漏标(通常很短)
- 清理(Cleanup):统计存活对象、重置分区信息,为复制做准备
关键点:写屏障开销小,SATB 日志异步批量处理,整体吞吐影响可控。


















