写屏障由JVM自动注入,启用G1/CMS等GC时,在对象字段赋值、数组元素写入等引用类型写操作后立即生效,用于标记跨代引用(如老→年)和捕获SATB断开引用,保障并发GC正确性。

写屏障不是开发者手动调用的 API,它由 JVM 自动插入并管理,你不需要、也不能直接“使用”它。
它在什么时机自动生效
只要启用了支持写屏障的垃圾收集器(比如 G1、CMS、ZGC 的部分模式),JVM 就会在以下操作发生时,于赋值语句前后自动注入写屏障逻辑:
- 对象字段赋值:如 obj.field = otherObj
- 数组元素写入:如 arr[i] = otherObj
- 局部变量或栈帧中引用更新(某些 GC 实现也覆盖)
注意:只有涉及引用类型的写操作才触发;基本类型、对象创建、方法调用本身不触发。
它对哪类跨代写操作敏感
写屏障主要关注可能影响年轻代 GC 正确性的引用变更,即:
- 老年代 → 年轻代:如 oldObj.child = youngObj → 触发卡表标记(dirty card)
- 灰色对象断开对白色对象的引用(SATB 场景):如 grayObj.field = null 或 grayObj.field = otherObj → 推旧引用入 SATB 缓冲区
而 年轻代 → 老年代 或 同代内引用 不会触发写屏障——因为它们不影响年轻代回收的安全性。
它背后干了哪些关键事
写屏障本身是一小段高效汇编或 C++ 内联代码,嵌入在 JIT 编译后的机器指令中。它的实际工作包括:
- 快速判断目标对象是否跨代(通过地址范围比对)
- 计算目标对象所在“卡页”索引(如地址右移 9 位对应 512 字节卡页)
- 原子标记卡表对应字节为 dirty(启用 -XX:+UseCondCardMark 时先检查是否已脏)
- 若处于并发标记阶段,将被覆盖的旧引用存入线程本地 SATB 缓冲区,后续由后台线程批量处理
你能做的配置与观察
虽然不能编码调用,但你可以通过参数参与其行为调控:
- 启用 G1 时,默认开启写屏障:-XX:+UseG1GC
- 优化卡表写入开销:-XX:+UseCondCardMark(避免重复标记同一卡页)
- 查看卡表相关统计:-XX:+PrintGCDetails -XX:+PrintAdaptiveSizePolicy(日志中可见 Dirty Card Queue 处理情况)
- 禁用写屏障?不行。HotSpot 会通过内存保护页等机制强制拦截 Unsafe 或 JNI 的绕过行为
本质上,写屏障是 GC 正确性和高性能之间的底层契约——你写 Java 代码时完全无感,它却默默确保每次引用变化都被精准捕获。

















