cms确实存在因引用删除导致的漏标风险,根本原因在于其增量更新机制仅捕获新增引用、不处理引用删除,且重新标记阶段无法回溯已消失的存活路径。

CMS 垃圾收集器在并发标记阶段允许用户线程与 GC 线程同时运行,这带来一个关键风险:黑色对象(已标记完成)可能在标记过程中新增对白色对象(尚未标记)的引用,导致该白色对象被漏标——即本应存活却被错误回收。重新标记阶段(Remark)正是为修正这类漏标而设,其核心任务之一就是处理“新增变量引用”。
为什么新增引用必须在重新标记阶段补救
并发标记采用三色标记法,一旦对象被标记为黑色,GC 线程默认不再遍历它的引用。但应用线程可能在此后执行类似 A.field = B 的操作,使黑色对象 A 指向一个仍为白色的存活对象 B。此时 B 不会被后续标记过程覆盖,除非有机制主动捕获并回溯这一变更。
写屏障是捕获新增引用的技术基础
CMS 在每次对象引用赋值(尤其是跨代或涉及老年代对象时)前/后插入写屏障代码。当检测到黑色对象新增对白色对象的引用,写屏障会:
- 将该引用(如 A → B)记录进一个增量更新队列(通常称“mod-union table”或“dirty card queue”的变体)
- 触发“增量更新策略”:把源对象 A 从黑色重新标记为灰色,确保它会在重新标记阶段被再次扫描
- 该过程不依赖时间戳或日志,而是实时拦截、即时响应
重新标记阶段如何具体处理这些新增引用
重新标记是一个 Stop-The-World 阶段,但它只扫描有限范围,重点包括:
- 所有被写屏障记录的“新增引用”条目(即增量更新队列中的内容)
- 被重新标记为灰色的对象(如前述的 A),并递归扫描其全部引用字段
- 根集合(GC Roots)及新生代中可能引用老年代对象的跨代指针(通过卡表定位)
例如,若队列中存有“A.field → B”,重新标记阶段会以 A 为起点重新遍历,从而发现并标记 B 及其可达对象,彻底消除漏标。
该方案的局限与权衡
增量更新只针对“新增引用”,不处理“引用删除”。如果灰色对象 C 原本引用白色对象 E,随后 C 删除该引用,而 E 仅靠这条路径存活,则 E 可能被漏标——CMS 不记录删除动作,也不在重新标记中回溯已消失的路径。这是 CMS 接受少量浮动垃圾(floating garbage)的设计取舍,以换取更短的停顿时间。











