shenandoah 实现变量移动不卡顿的核心是 brooks pointers(转发指针)+ 读屏障:在对象头前插入8字节原子引用字段,gc复制对象后用cas更新为新地址,读屏障在每次字段访问前检查并自动跳转至新副本,使用户线程无需stw即可始终访问最新数据。

Brooks Pointers 是什么?
它是在每个 Java 对象头前方静态插入的一个 8 字节原子引用字段,初始值指向对象自身。当 GC 线程开始将该对象复制到新内存区域(to-region)后,就用 CAS 原子更新这个字段,使其指向新副本地址。
读屏障如何拦截并重定向访问?
JVM 在每次通过引用读取对象字段前,自动插入一小段检查逻辑:
- 若该对象的 Brooks Pointer 指向自身 → 正常读取,无额外开销
- 若已更新为新地址 → CPU 自动跳转,后续所有字段读取都落在新副本上
- 即使多个线程同时读同一个旧地址,CAS 更新保证一致性,读屏障确保跳转即时生效
为什么这能消除标记/移动阶段的 STW?
传统收集器(如 G1)必须 STW,是因为它不允许用户线程在对象迁移中途访问旧地址——否则会读到半拷贝、脏数据或崩溃。Shenandoah 不禁止访问旧地址,而是让旧地址“始终可用”,靠转发指针兜底。
- 栈上变量、寄存器里的旧引用无需立刻更新;GC 后续可并发地慢慢修正(Concurrent Update References 阶段),非必需
- 标记阶段只扫描对象图,不移动对象,天然并发
- 移动阶段由 GC 线程异步执行,用户线程靠读屏障自动跟随,全程无感知
实际影响与边界要注意
这套机制不是零成本:
- 每个对象多占 8 字节内存(Footprint 上升)
- 每次对象字段读取增加一次间接寻址(读屏障开销,现代 CPU 分支预测优化后约 1–2 纳秒)
- JIT 编译器需识别并内联读屏障逻辑,否则性能受损;部分逃逸分析或锁优化可能受限
- 仅初始标记和最终标记需 STW,且停顿时间恒定(通常 sub-10ms),与堆大小无关










