三色标记本身不保证并发安全,真正起作用的是配套的写屏障机制;漏标需同时满足黑色对象新增指向白色对象的引用和灰色对象断开对该白色对象的引用,cms用增量更新、g1用satb写屏障拦截并补偿,重新标记阶段兜底修正。

三色标记本身不保证并发安全,真正起作用的是配套的写屏障机制。没有写屏障,多线程环境下标记结果必然出错。
漏标问题的本质是引用变更未被感知
当用户线程在标记过程中修改对象引用时,若 GC 线程已将某黑色对象(如 A)及其子引用扫描完毕,而此时用户线程断开灰色对象 B 对白色对象 D 的引用,并让黑色对象 A 新增对 D 的引用,D 就会因未被重新扫描而被误判为垃圾——这就是漏标。
漏标成立需同时满足两个条件:
- 黑色对象在被标记完成后新增了对白色对象的引用
- 灰色对象在完成自身子引用扫描前,删除了对同一白色对象的引用
CMS 用增量更新(Incremental Update)堵漏
CMS 在用户线程修改引用时插入写屏障,一旦发现“黑→白”新增引用,就立刻把白色对象标记为灰色,并加入待扫描队列。这样即使 A 新增了对 D 的引用,D 也会被拉回灰色集合,后续被扫描到,避免误回收。
典型实现方式是卡表(Card Table)+ 写屏障:每次赋值操作触发屏障,记录跨代引用变更,供重新标记阶段快速补漏。
G1 用原始快照(SATB)保快照一致性
G1 在并发标记开始时拍下堆内存的逻辑快照,之后所有引用删除操作都会被写屏障捕获并记录旧引用(即被删掉的对象),GC 线程按快照继续标记。即使 B 删除了对 D 的引用,只要快照中 D 是可达的,它就不会被漏掉。
SATB 的核心是“只记删,不记增”:新增引用默认视为存活(本轮不处理),删除引用则入队等待重标记——这天然规避了漏标,但会引入少量浮动垃圾。
重新标记阶段是兜底关键
无论 CMS 还是 G1,都会在并发标记结束后安排一次短暂 STW 的重新标记(Remark)阶段。它不是从头扫描,而是聚焦于写屏障记录的“脏卡”或 SATB 队列中的旧引用,快速修正并发期间的遗漏。
这个阶段耗时取决于并发期间的引用变更频率,而非堆大小,因此可控性较强。没有它,漏标风险无法收敛。










