三色标记算法核心是并发准确识别存活对象:白色为未访问、灰色为已访问但引用未扫完、黑色为已访问且引用全扫完;关键约束是黑对象不可直接引用白对象,漏标需同时满足“黑→白新增引用”和“灰→白引用断开”两个条件。

三色标记算法的核心,是让垃圾回收在业务线程不停止的前提下,安全、准确地识别出哪些对象还“活着”。它不靠一口气扫完整个堆,而是把标记过程拆成小步,用颜色状态来记录每个对象的扫描进度。
三种颜色分别代表什么
白色:还没被 GC 线程看到,生死未定;灰色:已被看到,但它的引用还没全检查完,是当前正在处理的“前线”对象;黑色:自己和它所有直接引用的对象都已确认存活,标记完成。
关键不变量是:黑色对象不能直接引用白色对象。一旦出现这种情况,说明有活对象没被标上,可能被误删——这就是漏标问题的根源。
标记过程是怎么推进的
初始时所有对象都是白色;GC Roots(比如栈里的局部变量、静态字段)直接引用的对象被标为灰色;之后反复从灰色集合中取一个对象,把它引用的白色对象涂灰,自己涂黑;直到灰色集合为空。
这个过程可以和业务线程并发跑,但业务线程改引用关系时,会打破“黑不引白”的规则,所以必须干预。
并发时怎么防止出错
业务线程一边运行,一边可能做两件事:
- 把一个黑色对象新增指向白色对象的引用(比如 a.b = newObj)
- 把一个灰色对象断开对白色对象的引用(比如 a.b = null)
前者容易导致“多标”,产生浮动垃圾(无害,下次再清理);后者容易导致“漏标”,把活对象当垃圾清掉(危险)。
不同回收器应对策略不同:
- CMS 用增量更新(Incremental Update):发现黑→白新增引用,就把那个黑色对象重新拉回灰色,后续重标
- G1 和 ZGC 用原始快照(SATB):在灰色对象断开引用前,先记下那个白色对象,确保它不会被漏掉
为什么需要一次短暂 STW 的“重新标记”阶段
因为并发标记期间,应用线程一直在动引用关系,GC 线程看到的状态是“过期快照”。所以最后要停一下,把那些因并发修改而可能漏掉的对象补标一遍。这一步时间很短,因为大部分对象早已标完,只剩少量灰色或新暴露的问题需校正。











