shenandoah通过jvm在字节码阶段主动插入读写屏障逻辑,结合brooks转发指针实现并发gc:读屏障检查fwdptr并重定向引用,写屏障协调写入时机并支持预转发,全程无硬件依赖、无需stw(除根扫描),兼容性强但有轻微cpu开销。

Shenandoah 的读写屏障不是靠硬件指令直接完成的,而是由 JVM 在字节码编译或解释执行阶段,**主动插入特定的屏障逻辑代码**,再配合对象头结构与运行时状态协同工作。它的核心目标是:在不暂停应用线程的前提下,让 Mutator(用户线程)对对象的访问始终看到“正确且一致”的数据,哪怕该对象正在被 GC 线程并发移动。
读屏障:确保读取的是迁移后的新对象
当应用线程执行 getfield、aload 等涉及对象引用读取的操作时,JVM 会在其前后插入读屏障逻辑。典型流程如下:
- 检查当前对象头中是否已设置 Brooks 转发指针(fwdptr),且该指针非空
- 若 fwdptr 存在且指向新位置,直接返回新地址上的对象引用
- 若 fwdptr 尚未设置(对象未开始移动)或仍指向自身,则原样返回原对象
- 整个过程发生在用户线程上下文中,无需锁或系统调用,开销可控
写屏障:控制对象更新时机并辅助转发
写屏障主要作用于 putfield、astore 等修改对象字段或引用的操作。它不只是拦截写入,更承担状态协调职责:
- 先判断当前 GC 是否处于并发转移阶段(通过全局状态标志位)
- 若处于转移中,且待写入的对象已有有效 fwdptr,则将写操作重定向到新对象上
- 若对象尚未转发,但 GC 已标记为“待转移”,部分实现会触发“预转发”——协助 GC 线程完成复制并设置 fwdptr
- 避免出现“旧地址写入后,GC 再移动”的数据错乱问题
Brooks 指针与屏障协同的关键设计
Shenandoah 使用对象头前附加的转发指针(早期版本固定 +8 字节;2.0 版本优化为临时复用 Mark Word),这是所有屏障行为的锚点:
- 读屏障依赖 fwdptr 判断是否需重定向,本质是“懒加载式重映射”
- 写屏障不仅检查 fwdptr,还会在必要时参与 CAS 设置,保证多线程下转发动作原子性
- 屏障只对引用类型字段生效(如
Object、数组引用),基本类型(int、long)不触发,降低开销 - 根集合(GC Roots)扫描仍需短暂 STW,但耗时极短,与堆大小无关
与 ZGC 染色指针的根本区别
ZGC 把元信息(如是否已重定位)编码进 64 位指针的高位,靠硬件支持的内存访问陷阱(trap)触发处理;Shenandoah 则完全走软件路径:
- 不依赖特殊 CPU 指令或操作系统 trap,兼容性更强,可在 JDK 8u 上运行
- 屏障逻辑由 JIT 编译器内联展开,热点路径可高度优化
- 代价是每个对象增加存储开销(早期明显,2.0 已缓解),且屏障本身带来轻微 CPU 周期消耗











