satb写屏障仅在对象字段被覆盖且旧引用指向老年代(或收集集)时触发,不处理读、分配、方法调用及数组赋值;其正确性依赖initial mark的stw以确保根集合完整,否则会导致漏标和浮动垃圾。

G1 的 SATB 写屏障只拦截特定赋值动作,不是所有写操作都触发,理解这点是读懂并发标记逻辑的前提。
什么赋值会触发 SATB 写屏障?
SATB 写屏障只在满足「对象字段被覆盖」且「旧引用指向老年代(或当前收集集)」时才生效。它不关心读、分配、方法调用,也不处理数组元素赋值(那是 card table 的事)。
-
oldObj.field = anotherOldObj→ 触发:老-老引用,旧值需记录进satb_mark_queue -
oldObj.field = youngObj→ 不触发:目标是年轻代,G1 不通过 SATB 管理跨代弱引用 -
youngObj.field = oldObj→ 通常不触发:源对象在年轻代,即使赋值后形成跨代引用,也由 remembered set + card table 处理 -
array[i] = obj→ 不触发:数组元素更新走的是 write barrier for card marking,和 SATB 无关
为什么必须配合 Initial Mark 的 STW?
因为 SATB 的“快照”本质是基于初始标记那一刻的根集合构建的。如果根没扫全,后续所有 SATB 记录的旧值就失去了锚点——你连“谁活过”都不知道,还记什么“谁被删了”?
- 没有 STW 的 Initial Mark,
satb_mark_queue里压入的对象可能根本不在初始快照链上 - 最终标记(Remark)阶段要处理的不只是队列残留,更是补全那些因根扫描不完整而漏掉的灰色起点
- 一旦 Initial Mark 漏掉某个活跃线程栈里的局部变量,它所引用的老年代对象哪怕被 SATB 记录了旧值,也不会被真正标记为存活
并发标记期间 satb_mark_queue 积压意味着什么?
积压不是性能瓶颈本身,而是应用行为与 G1 标记节奏不匹配的信号:大量老年代对象字段被高频修改(比如缓存热更新、配置重载),导致写屏障持续压入旧引用,而并发标记线程来不及消费。
- 积压超过阈值会触发额外的 STW 暂停(可通过
-XX:+PrintGCDetails -XX:+PrintGCApplicationConcurrentTime观察到 “SATB queue overflow”) - 这不是 GC 参数调优能根治的问题,得看业务代码是否真有必要频繁覆盖老年代对象的引用字段
- 积压严重时,G1 可能提前结束并发标记,转入 Remark,导致混合 GC 延迟或回收效率下降
真正容易被忽略的是:SATB 不保证“零浮动垃圾”,它只保证“初始快照中活着的对象不会被误收”。而那个快照的完整性,完全依赖 Initial Mark 阶段的 STW 质量——哪怕只漏一个根,整个并发标记的保守性就塌了一角。










