zgc读屏障是jit静态内联的汇编逻辑,插入在aload、getfield、getstatic、arraylength、aaload、checkcast、instanceof等字节码处,通过3–7条原子指令实现零卡顿指针重定向,无需函数调用、锁或内存分配。

ZGC 的读屏障不是运行时拦截器,也不是靠函数调用实现的“修复”,而是在字节码执行前,由 JIT 编译器静态内联的一小段汇编逻辑。它不卡顿,是因为全程无函数调用、无锁、无内存分配,且绝大多数情况下只在首次访问染色指针时触发。
读屏障插入在哪几类字节码上
JIT 编译器会在所有可能加载对象引用的字节码处自动插入 Load Barrier 逻辑,主要包括:
- aload / aload_0~aload_3:加载局部变量表中的对象引用
- getfield / getstatic:读取实例字段或静态字段(若字段类型为引用)
- arraylength / aaload:访问数组长度或从对象数组中读取元素
- checkcast / instanceof:类型检查前需确保引用地址有效
注意:像 unsafe.getLong(obj, offset) 或 JNI 中直接解引用 jobject 地址的操作,会绕过屏障——这些路径必须由开发者显式规避或改用安全 API。
屏障内联逻辑如何做到“零卡顿”
每条受保护的 load 指令,对应生成的机器码大致等价于以下三步原子操作(x86-64 示例):
- 读取原始指针值(如从字段偏移处 load 一个 8 字节值)
- 对指针做位运算:test rax, 0x0000F00000000000(检查高 4 位颜色位)
- 若命中 Marked0 或 Marked1,跳转到极短的 barrier stub;若为 Remapped,直接清除颜色位并返回地址;若全为 0,则原样通过
整个过程通常仅需 3–7 条 CPU 指令,无分支预测失败惩罚,无 cache miss(转发表 entry 通常已预热),且 barrier stub 本身也经过高度优化:查 forwarding table 是一次 cache-line 对齐的 load,重写寄存器值是单条 mov,最后用 store_release 原子更新本地引用。
为什么“修复”不阻塞线程也不影响吞吐
关键在于 ZGC 不要求“全局一致”的指针状态,而是接受“最终一致”+“按需自愈”:
- 应用线程读到 Marked 指针 → 触发 barrier → 查表得新地址 → 原子更新自己寄存器里的副本 → 后续所有对该引用的使用都指向新位置
- 这个更新只改当前线程栈/寄存器中的副本,不影响其他线程——其他线程下次读到该字段时,同样触发 barrier 自行修正
- GC 线程只需保证 forwarding table 在 relocate 阶段写入完成、且对所有 CPU 可见(靠 store-release + 内存屏障),无需同步等待任何应用线程
因此没有锁竞争,没有跨核通信开销,也没有 STW 协调成本。所谓“修复”,本质是每个线程在访问瞬间完成一次轻量级本地化重定向。
Remapped 状态为何是性能关键分水岭
一旦对象完成 relocate 并被标记为 Remapped,后续所有对该对象的访问都不再进入 barrier 分支:
- 因为 JIT 编译器在生成代码时,可基于 profile data 判定某字段长期返回 Remapped 指针,进而彻底省略颜色位检测
- 即使未优化,test + jz 的代价也远低于一次 cache miss 或函数调用
- 实际压测中,95% 以上的对象字段访问在稳定期走的是“零开销路径”
这正是 ZGC 能把停顿与堆大小解耦的核心机制:屏障开销不随堆增长,只与活跃对象的并发转移频率相关,而后者由 GC 线程主动控制节奏。











