JVM中怎样利用增量更新(Incremental Update)与原始快照(SATB)破解漏标难题

作者:袖梨 2026-07-28
JVM并发标记漏标问题由应用线程修改引用导致三色标记滞后,CMS用增量更新(写屏障拦截黑→白新增引用并重标灰色)防御,G1用原始快照(SATB,写屏障记录灰→白删除前的旧引用)防御,二者不可混用。

JVM并发标记阶段的漏标问题,本质是应用线程修改引用关系时,三色标记状态跟不上变化——黑色对象直接指向白色对象,或灰色对象删掉对白色对象的引用,都会导致本该存活的对象被误判为垃圾。增量更新和原始快照不是可选技巧,而是CMS与G1各自选定的、必须启用的底层防御机制。

增量更新:盯住“黑→白”新增路径

它假设最危险的操作是黑色对象突然新增对白色对象的引用——这会绕过标记流程,让白色对象彻底“隐身”。CMS用写屏障实时拦截这类赋值动作:

  • 当执行 obj.field = newObj 且 obj 已是黑色、newObj 是白色时,立即把 newObj 重新标记为灰色,塞入扫描队列
  • 不关心灰色对象是否删引用,也不处理新分配对象的初始颜色,只专注堵住“黑→白”这条漏标主通道
  • 代价是每次字段写入都触发屏障,高频 setter 或集合 add 场景下有轻微性能开销

原始快照(SATB):捕获“灰→白”删除瞬间

G1换了一种思路:不拦新增,而是抢在引用被覆盖或置 null 前,快照下那个即将消失的旧对象。它信任标记开始时的存活图,确保“曾经活过”的对象不会被跳过:

  • 当执行 obj.field = nullobj.field = otherObj 时,写屏障把原 field 指向的对象(哪怕已是白色)记入 satb_mark_queue
  • 后续重新标记阶段统一扫描这些“曾活过的对象”,补全其可达子图
  • 写屏障只在引用变更时触发,比 CMS 更轻量;但可能多标已失效对象,产生稍多浮动垃圾

二者不可混用,选择取决于收集器设计目标

CMS面向低延迟,倾向精准控制,宁可多花点写入开销也要避免漏标;G1面向大堆与混合回收,优先压低写屏障频率,用可控的多标换取更稳的 STW 时间。实际配置中:

  • CMS 必须启用 -XX:+CMSEdenChunksRecordAlways 等配套参数,否则增量更新无法生效
  • G1 的 -XX:+G1UseSATBBarrier 默认开启,禁用即破坏并发标记正确性
  • 无论哪种方案,初始标记(Initial Mark)都必须 STW 完成,这是三色标记成立的前提

理解它们的关键,不是背定义,而是抓住动作触发点:一个抓“新增”,一个抓“删除”;一个拉回灰色,一个快照旧引用。机制不同,目标一致——守住那条不能越界的生死线。

相关文章

精彩推荐