Shenandoah通过Brooks转发指针实现并发对象移动:在对象头插入原子可更新的引用字段,配合读屏障自动跳转至新副本,确保用户线程访问始终命中最新地址,仅初始和最终标记需短暂STW。

Shenandoah 收集器的并发移动能力,核心就落在 Brooks Pointers(转发指针) 这一设计上。它让对象在被复制到新内存位置的过程中,无需暂停用户线程,也能保证所有访问始终命中最新副本。
为什么需要转发指针?
传统回收器(如 G1)在疏散(evacuation)阶段必须暂停用户线程,因为一旦对象从 from-region 搬到 to-region,旧地址上的引用若被访问,就会读到过期数据或引发错误。而 Shenandoah 要全程并发,就必须解决“旧地址还能被访问”这个现实问题。
转发指针不依赖操作系统陷阱或页保护机制——那些会触发用户态到内核态切换,开销大、延迟高。它选择在每个对象头前**静态插入一个原子可更新的引用字段**,初始时指向对象自身;当对象被复制后,该字段被 CAS 更新为指向新副本地址。
Brooks Pointers 如何支撑并发访问?
关键在于:每次通过对象引用读取字段前,JVM 插入了一段轻量级的 读屏障(Read Barrier) 逻辑。它会检查该对象是否处于疏散中,并自动跳转到转发指针所指的新位置。
- 用户线程访问旧对象 → 触发读屏障 → 查看其 Brooks Pointer → 若已更新,直接跳转至 to-region 的副本
- 多个线程可能同时读同一个旧对象,但转发指针是原子更新的,不会出现中间态错乱
- 即使部分引用尚未更新(比如还在栈或寄存器里的旧指针),也能靠读屏障“兜底”,确保语义一致
转发指针带来的实际行为变化
它改变了 JVM 对“对象位置”的抽象方式:对象不再有唯一固定地址,而是存在“逻辑身份”与“物理位置”的分离。用户代码看到的是稳定的身份,底层内存布局可以动态漂移。
- 写操作仍作用于逻辑对象,读屏障自动路由到最新副本,对上层透明
- GC 线程完成复制后,只需原子更新转发指针,无需同步扫描所有引用点
- 后续引用更新(Concurrent Update References 阶段)是优化项,非必需;读屏障保障了即时一致性
它不是万能的:代价与边界
转发指针带来额外内存开销(每个对象+8字节)、每次对象访问增加一次指针间接跳转(读屏障开销),以及对 JIT 编译器的适配要求。但它换来了极短的 STW:仅初始标记和最终标记需停顿,且时长与堆大小无关。
这种设计把“移动对象”的复杂性封装在 GC 和运行时内部,让应用开发者完全无感——变量照常使用,对象照常传递,只是背后早已悄然换了地方。










