漏标需同时满足“黑→白”新增引用和原灰色路径被切断;cms用增量更新写屏障捕获新增引用并重新标记黑色对象为灰色,但不处理引用删除,以换取更短stw。

CMS 的并发标记与并发清理阶段确实存在变量不一致风险,核心在于用户线程持续修改引用,而 GC 线程按固定节奏推进,二者不同步会破坏三色标记的逻辑前提。这种不一致不表现为“随机错乱”,而是有明确触发条件和对应防护机制——关键在漏标(mis-marking),而非误删(mis-reclaim)。
漏标发生的两个硬性前提
三色标记中,黑色对象代表“已处理完毕”,其引用关系被默认冻结。一旦它新增指向白色对象,又没有补偿机制,就会漏标。但仅新增引用还不够,必须同时满足:
- 一个黑色对象 A 在标记完成后,执行了 A.field = B,其中 B 当前是白色
- 原本能带出 B 的灰色路径被切断,例如灰色对象 C 原本持有 B 的引用,此时执行了 C.field = null
两者缺一不可。只发生新增引用,B 仍可能被其他灰色对象带入扫描;只发生引用删除,B 若本身已被标记或有其他路径,也不会漏。只有“黑→白”新增 + “灰→白”路径断裂,才真正打破三色不变式。
写屏障如何实时拦截新增引用
CMS 采用增量更新(Incremental Update)策略,依赖写屏障在赋值操作后介入:
- 当 JVM 执行类似 objA.field = objB 时,若 objA 是黑色、objB 是白色,写屏障立即捕获该操作
- 将 objA 重新标为灰色,并把该引用(A → B)记入增量更新队列(如 mod-union table)
- 等到重新标记(Remark)阶段 STW 时,GC 线程扫描该队列,以 objA 为起点重新遍历,从而发现并标记 B 及其可达对象
这个过程不依赖时间戳或快照,是即时响应的“事后补救”,确保所有被黑色对象新拉进来的存活对象都能被覆盖。
为什么引用删除不被处理——CMS 的设计取舍
CMS 明确不记录、不回溯引用删除动作。例如灰色对象 C 删除对白色对象 E 的引用后,若 E 再无其他路径,它就永远留在白色状态,被当作垃圾回收。
- 这种漏标无法在重新标记阶段修复,因为没有机制知道“C 曾经引用过 E”
- 这是 CMS 主动接受的代价:用少量浮动垃圾(floating garbage)换取更短的 STW 时间
- 它不是 bug,而是权衡结果——G1 用 SATB(赋值前快照)来覆盖删除场景,但代价是更高的写屏障开销和内存占用
并发清理阶段的“安全边界”在哪
并发清理本身不产生漏标,但它放大了标记阶段遗留问题的影响:
- 清理阶段只清除“重新标记结束时确认为垃圾”的对象,不重新扫描,所以标记阶段漏掉的存活对象不会被误删(写屏障已保障)
- 但标记阶段漏掉的垃圾对象(即本该回收却没标上的),会在清理阶段被跳过,变成浮动垃圾,堆积在老年代
- 浮动垃圾越多,越容易触发 Concurrent Mode Failure——这不是清理逻辑错误,而是标记精度不足+并发窗口过长的连锁反应
因此,并发清理的风险不在“清错”,而在“清不净”,根源仍在并发标记阶段的状态同步是否严密。











