g1采用satb(起始快照)方案,通过写屏障在引用被覆盖前记录旧值,确保初始标记时存活的对象不被漏标;cms采用增量更新(iu)方案,在黑色对象新增指向白色对象时将其重新标记为灰色,防止新引用导致的漏标。

G1 和 CMS 都采用并发标记,但解决漏标问题的思路根本不同:CMS 用增量更新(IU)“补新引用”,G1 用 SATB “记旧引用”。这不是实现细节差异,而是对“什么该被保护”这件事的哲学分歧。
漏标发生的两个典型路径
并发标记中,GC 线程和业务线程并行运行,对象引用随时可能变化。漏标只发生在白色对象上,且必须满足以下任一条件:
- 一个已标记为黑色的对象,新增了对某个白色对象的引用
- 一个灰色对象,删除了对某个白色对象的唯一引用
前者是“新连进来”,后者是“老路断掉了”。CMS 和 G1 分别盯住其中一条路径重点防御。
CMS 的增量更新(IU):拦截新增引用,把白色对象拉回灰色队列
CMS 在写屏障中检测到 黑色对象赋值给白色对象(如 obj.status = new Status()),就立刻将那个白色对象重新标记为灰色,加入扫描队列。它不关心引用是否被删,只确保“新挂上的不能丢”。
- 每次引用赋值都要走写屏障,开销稳定但略高
- 不处理“灰色删引用”路径,靠最终标记(Remark)阶段全局重扫卡表兜底
- Remark 阶段需扫描 dirty card + GC roots,STW 时间易抖动
G1 的 SATB:在引用被覆盖前记录旧值,以初始快照为安全依据
G1 的写屏障只在 已有对象字段被覆写前 触发(如 obj.field = otherObj),把原字段值(可能是 null 或某个老年代对象)压入线程本地的 satb_mark_queue。它不拦新增,专盯“消失”。
- 仅拦截跨代或涉及收集集的引用赋值,年轻代内赋值通常不触发
- 依赖初始标记(Initial Mark)阶段的 STW 扫描根集合,形成完整快照起点
- 最终标记阶段只需处理 SATB 队列里的对象,无需扫全卡表,Remark 更轻量
为什么 SATB 更适合 G1 的设计目标
G1 面向大堆、低延迟场景,要求 STW 尽可能短且可预测。SATB 把标记范围锁死在初始快照,后续只追“被破坏的点”,避免 CMS 那种动态增长的标记工作集。即使应用频繁更新缓存对象字段,只要 SATB 队列不持续积压,整体暂停时间就更平稳。











