satb机制能可靠防止漏标,即确保初始快照中可达的对象不被误回收;它通过写屏障拦截旧引用并延迟重标记来实现,但会容忍浮动垃圾和非实时性。
原始快照(satb,snapshot at the beginning)机制并不能“完美”保障一致性,但它能**可靠地防止对象漏标**——这是并发可达性分析中最危险的问题。所谓“完美”,在工程实践中并不存在;satb 的目标是用可接受的开销,换取强正确性保证:绝不误回收存活对象。
为什么 SATB 能守住底线:它锁住的是“开始那一刻的引用视图”
在并发标记启动瞬间,SATB 不冻结整个堆,而是记录下此时所有活跃引用关系的快照。后续用户线程对引用的修改(比如断开、重赋值),不会影响这个快照中已存在的可达路径。GC 线程按这个快照继续扫描,即使中间引用被删,只要该对象在快照里已被灰色/黑色节点间接指向,它就不会被漏掉。
关键不在于“全程不变”,而在于确保所有在标记开始时可达的对象,最终一定被标记为黑色。
SATB 的核心动作:写屏障拦截 + 延迟重处理
它依赖 JVM 在对象引用字段被修改时触发的写屏障(Write Barrier),具体做两件事:
-
拦截删除操作:当用户线程执行
obj.field = null或obj.field = otherObj(原引用被覆盖)时,若被覆盖的旧引用指向一个白色对象,SATB 会把该旧引用记录进一个“待重新标记”的缓冲区(SATB Mark Queue); -
不干预新增引用:新增引用(如
newObj.field = whiteObj)无需拦截,因为新引用指向的白色对象本来就不在初始快照里,即使没被这次标记到,也只是产生浮动垃圾,无害。
标记主流程结束后,GC 会批量扫描 SATB 队列里的旧引用所指向的对象及其子图,确保它们被正确标记——这步叫“重新标记(Remark)”,是 STW 阶段,但耗时远小于全堆扫描。
它不解决什么,也不追求什么
SATB 明确接受两类“不完美”:
- 浮动垃圾(Floating Garbage):标记期间新创建、且未被任何灰色/黑色对象引用的对象,会被留在白色集合中,本次不回收,留待下次 GC —— 这是主动权衡,换来了低延迟;
- 非实时性:它不保证标记结果与任意时刻的堆状态完全一致,只保证初始快照中所有可达对象不被漏标。一致性是逻辑一致性,不是时间一致性。
典型落地:G1 收集器如何用 SATB
G1 将堆划分为 Region,SATB 写屏障仅作用于跨 Region 的引用更新(比如老年代 Region 中的对象修改了对新生代 Region 的引用)。它用卡表(Card Table)辅助定位脏卡,并配合每个 Region 自带的 TAMS(Top-at-Mark-Start)指针,精准界定“快照开始后新分配的对象区域”,避免扫描这些对象干扰标记结果。这种设计让 G1 在大堆场景下仍能控制 Remark 时间在毫秒级。










