SATB通过Initial Mark阶段STW构建初始存活视图,并结合写屏障记录旧引用,确保初始存活对象不被误回收;它允许浮动垃圾以降低并发开销。

SATB(Snapshot-At-The-Beginning)是G1收集器在并发标记阶段保障标记正确性的核心机制,它不追求“实时同步”,而是通过一个逻辑快照+写屏障协作的方式,在用户线程持续运行的前提下,确保初始时刻存活的对象不会被误回收。
快照不是内存拷贝,而是逻辑锚点
SATB的“快照”并非复制所有对象状态,而是在Initial Mark阶段一次STW中完整扫描根集合(线程栈、全局变量、JNI引用等),以此构建出一个可靠的“初始存活视图”。此后所有标记工作都基于这个视图展开——只要一个对象在Initial Mark时可达,它在整个并发标记周期内就被视为存活,哪怕后续被断开引用。
这个快照的完整性完全依赖Initial Mark的STW质量:漏掉一个活跃栈帧里的老年代引用,就可能让其指向的对象彻底脱离标记链,最终被错误回收。
SATB写屏障只拦截特定赋值动作
G1的SATB写屏障由JIT在编译期插入,仅在满足两个条件时触发:
立即学习“Java免费学习笔记(深入)”;
- 赋值目标是**已存在对象的引用类型字段**(如obj.field = newRef)
- 该对象位于**老年代或当前收集集(CSet)中**,且被覆盖的旧值非null、且指向老年代(或CSet)
它明确不处理以下情况:
- 年轻代对象之间的赋值(如youngObj.ref = anotherYoungObj)
- 数组元素赋值(arr[i] = obj)——走的是card table写屏障
- 对象分配、方法调用、读操作、赋值为null(即旧值为null时不记录)
旧值入队,而非新值追踪
当oldObj.field = anotherOldObj发生时,JVM在赋值前把oldObj.field原来的值(比如prevOldObj)压入线程本地的satb_mark_queue。这个旧值将成为后续重新扫描的起点之一,用于补全因引用变更而断裂的快照链。
这与CMS的增量更新(IU)方向相反:CMS在写屏障中对新引用做“再标记”,G1则通过旧引用“回溯快照”。两者都解决漏标,但策略不同。
标记结果安全,但允许浮动垃圾
SATB只保证一件事:Initial Mark时活着的对象,绝不会被本轮GC误收。但它不保证所有后来变活的对象都被及时标记——比如并发标记中新建的老年代对象,或通过SATB队列中新发现的旧引用间接抵达的对象,可能来不及被完全扫描。
这部分未被标记但实际存活的对象,就是“浮动垃圾”,会留在堆中进入下一轮GC处理。这是SATB为降低并发标记开销所作的合理取舍,不是缺陷。


















