写屏障是JVM在字段赋值前后自动插入的轻量钩子,核心目标是让GC在并发状态下准确感知引用关系变化;分为pre-write(捕获旧引用,防漏标)和post-write(维护跨区域引用,如标记卡页)两类。

并发标记中的写屏障不是用来“拦住”赋值操作的,而是 JVM 或运行时在字段赋值前后自动插入的轻量钩子,核心目标只有一个:让 GC 在并发状态下仍能准确感知引用关系的变化。
写屏障分两类,各司其职
不同 GC 算法对写屏障的使用方式不同,但主流设计(如 G1、ZGC、Go 的 GC)普遍区分两种时机:
- pre-write barrier(赋值前屏障):在 obj.field = new_value 执行之前触发,捕获即将被覆盖的旧引用。这是 SATB(Snapshot-At-The-Beginning)算法的关键——它把旧对象压入线程本地缓冲区,确保后续并发标记能重新扫描这些“可能丢失”的对象,防止漏标。
- post-write barrier(赋值后屏障):在赋值完成之后触发,主要用于维护跨区域引用信息。例如 G1 中,若新引用指向其他 Region,就标记对应卡页(Card)为 dirty,并异步更新目标 Region 的记忆集(RSet)。
变量引用追踪靠的是“变化感知”,不是全程监控
写屏障不记录所有读写,只聚焦于可能导致可达性改变的关键写操作。比如:
- 普通局部变量赋值(如 int x = 5)不触发写屏障,因为不涉及堆上对象引用;
- 对象字段写入(如 user.name = "Alice")、数组元素更新(如 arr[0] = obj)、静态字段修改(如 Config.cache = map)才可能触发;
- 是否真正执行屏障逻辑,还取决于 JIT 编译器优化结果——例如逃逸分析判定对象未逃逸,整个屏障可能被完全省略。
触发机制依赖运行时与编译器深度协同
写屏障不是 Java 方法调用,而是由 JIT 在生成机器码时直接内联的底层指令序列。这意味着:
- 它必须与 GC 算法状态同步(如当前是否处于并发标记阶段);
- 屏障逻辑需极轻量,通常只是原子写或缓存行友好型日志追加;
- Go 和 Java 的实现路径不同:Go 的写屏障由编译器在 SSA 阶段插入,而 Java 的 G1/ZGC 依赖 JVM 运行时配合 C2 编译器定制桩代码。
掌握要点:从三色标记漏洞反推屏障必要性
理解为什么需要写屏障,最有效的方式是看它解决什么问题。典型漏标场景如下:
- 标记已推进到黑色对象 A,A.field 指向白色对象 B;
- 应用线程执行 A.field = C,B 失去唯一强引用;
- 若无 pre-write barrier,B 将不再被任何灰色/黑色对象引用,也不会被重新扫描,最终被错误回收。
而写屏障在此刻捕获 B,并将其加入 SATB 日志,后续并发标记线程批量处理该日志,把 B “复活”为灰色重新遍历——这就是引用追踪在并发下的可靠性保障。










