g1写屏障是嵌入引用赋值后的底层机制,兼具维护卡表(标记跨代引用的脏卡)和支撑satb并发标记(捕获断开的旧引用以防漏标)双重职责,二者逻辑正交但共用同一入口。

G1收集器中的写屏障不是单一功能的钩子,而是同时承担卡表维护与并发标记修正两个关键职责的底层机制。它被精准植入在每次引用赋值操作(如 obj.field = newObj)之后,由JVM自动触发,不依赖Java代码编写,也不受开发者控制。
写屏障如何维护卡表(Card Table)
卡表是G1识别跨代引用的核心辅助结构,把堆内存划分为固定大小的“卡页”(通常512字节),每页对应卡表中一个字节标记。当对象引用发生跨代写入(比如年轻代对象引用老年代对象),写屏障会立即执行mark card操作,将对应卡页标记为dirty(脏卡)。
- 后续年轻代GC时,只需扫描所有dirty卡页内的对象,就能快速定位可能存在的老年代跨代引用,避免全量扫描整个老年代
- 参数
-XX:+UseCondCardMark启用“条件卡标记”,即写屏障先检查卡页是否已为dirty,避免重复写入和缓存行伪共享(false sharing)开销 - 卡表本身由
CardTableRS(Card Table Remembered Set)管理,其初始化阶段就绑定了对应的屏障集合(BarrierSet)
写屏障如何支撑并发标记(SATB)
G1采用SATB(Snapshot-At-The-Beginning)策略进行并发标记。写屏障在此阶段的作用不是记录新引用,而是捕获“被删除”的引用——即灰色对象断开对白色对象的引用。这是防止漏标的必要补偿。
- 当执行
obj.field = null或obj.field = otherObj(且原引用指向白色对象)时,写屏障会将被覆盖的旧引用推入SATB缓冲区 - 这些被“快照保留”的引用,会在并发标记后期被重新扫描,确保原本存活但被断开连接的对象不会被误标为垃圾
- 该逻辑与卡表标记正交:卡表关注“谁引用了我”,SATB关注“我曾引用过谁”,二者共用同一写屏障入口,但分支处理路径不同
写屏障的底层实现位置与开销特征
HotSpot中所有引用写入最终都落入oop_store模板函数,写屏障逻辑就嵌在赋值语句之后(*p = v;之后),属于C++层面的内联汇编增强。
- 触发无条件:只要启用了G1(
-XX:+UseG1GC),所有对象字段赋值、数组元素写入、局部变量存储等涉及引用变更的操作均生效 - 性能代价可控但真实存在:每次引用写都会增加几条原子指令(如store+conditional jump),高并发写密集场景下可观测到微小吞吐下降
- 不可绕过:哪怕使用
Unsafe.putObject或JNI直接写内存,HotSpot仍通过内存保护页或写时拦截技术确保屏障生效(ZGC/Shenandoah另用读屏障则机制不同)











