g1并发标记阶段基于initial mark的初始快照和satb写屏障保障安全性,不实时同步堆状态;快照是逻辑锚点而非内存拷贝,确保initial mark时可达对象全程视为存活;satb仅拦截老年代/cset中非null引用覆盖并记录旧值;允许浮动垃圾存在以降低开销;tams指针划分标记前后对象区域,上方新对象默认存活。

G1收集器的并发标记阶段不追求实时同步,而是靠“初始快照 + 写屏障协作”保障标记安全。它不扫描整个堆的最新状态,而是基于Initial Mark那一刻的存活视图逐步推进,同时用SATB机制捕获引用变更带来的影响。
快照不是复制,而是逻辑锚点
Initial Mark阶段的一次STW,会完整扫描所有GC Roots(线程栈、静态变量、JNI引用等),构建出一个可靠的“初始存活视图”。这个视图就是SATB的“快照”——它不是内存拷贝,而是一个逻辑起点:只要对象在Initial Mark时可达,整个并发标记周期内就视为存活,哪怕后续被断开引用也不会被误回收。
这个快照的质量直接决定标记结果是否可靠。如果STW期间漏掉某个活跃栈帧里的老年代引用,对应对象就可能彻底脱离标记链,最终被错误回收。
写屏障只记录被覆盖的旧引用
G1的SATB写屏障由JIT在编译期插入,仅对满足条件的赋值操作生效:
- 目标对象位于老年代或当前收集集(CSet)中
- 赋值的是对象的引用类型字段(如 obj.field = newRef)
- 被覆盖的旧值非null、且指向老年代或CSet
触发时,JVM在赋值前把旧值(比如prevOldObj)压入线程本地的satb_mark_queue,作为后续重新扫描的起点。它不处理数组元素赋值、年轻代间赋值、null赋值或对象分配等操作。
标记过程允许浮动垃圾存在
SATB只保证一件事:Initial Mark时活着的对象,绝不会被本轮GC误收。但它不保证所有后来变活的对象都被及时标记。
例如,并发标记过程中新分配的老年代对象、或通过SATB队列中新发现的旧引用间接抵达的对象,可能来不及被完全扫描。这部分未被标记但实际存活的对象就是“浮动垃圾”,会留在堆中进入下一轮GC处理。这是为降低并发开销所作的合理取舍。
TAMS指针界定新旧对象边界
TAMS(Top at Mark Start)是G1用于区分并发标记开始前后对象的关键指针:
- TAMS以下区域:存放标记开始前已存在的对象,G1会扫描这些区域来标记初始存活对象
- TAMS以上区域:存放并发标记过程中新分配的对象,它们默认视为存活,不参与本轮标记
这种划分让G1能明确区分“快照内对象”和“快照外对象”,避免重复扫描或遗漏新生对象。










