shenandoah使用转发指针(非brooks指针)实现并发压缩整理:对象头直接存储新地址,读屏障检查并重定向访问,避免全局引用更新,仅根集合更新需短暂stw,停顿可控在10ms内。

Shenandoah 使用的不是 Brooks Pointers,而是转发指针(Forwarding Pointer)——这是它实现低延迟并发移动的关键机制,常被误称为 Brooks 指针,但二者原理不同、应用场景也不同。
转发指针的本质:对象头里存新地址
Shenandoah 在对象头中预留空间,直接写入该对象将被复制到的目标 Region 地址(即“转发地址”)。当 GC 线程开始并发移动对象时,原位置对象保持可访问,而所有后续对该对象的访问,都会通过读屏障检查对象头是否含有效转发地址;若有,则自动重定向到新位置。
- 不修改引用本身:堆中所有指向旧对象的引用保持不变,避免了“更新所有引用”的全局扫描开销
- 一次写入,多次受益:对象头只在首次移动时写一次转发地址,后续访问无需重复计算或查表
- 天然支持并发:用户线程与 GC 线程可同时运行,读屏障确保每次读取都看到一致视图
为什么能减少寻址开销?
传统移动式 GC(如 Serial、Parallel)需暂停应用线程(STW),遍历整个根集和堆内引用,逐个更新指向旧地址的指针。这涉及大量内存遍历和随机写操作,寻址路径长、缓存不友好。
- Shenandoah 把“寻址修正”从批量后台任务,下沉为单次读取时的轻量判断——仅检查对象头 + 条件跳转
- 转发地址是局部数据(紧邻对象元信息),CPU 缓存命中率高;相比跨 Region 查引用映射表,延迟更低
- 读屏障逻辑极简:若
obj->forward_ptr != null,则用它替代obj继续访问——一次分支预测 + 一次加载即可完成重定向
与 Brooks Pointers 的关键区别
Brooks 指针是另一种间接寻址思路:把每个对象变成一个指针容器,原始字段全部移入堆外结构,对象本体只存一个指向该结构的指针。每次访问字段都要多一次解引用。Shenandoah 并未采用这种设计。
- Brooks 需改造对象布局、增加 indirection 层,对 JIT 优化不友好;Shenandoah 保持对象格式不变,兼容性强
- Brooks 在每次字段访问时强制两次加载(先读指针,再读目标);Shenandoah 只在对象被移动后、且首次被读取时触发一次重定向,之后新引用直接指向新地址
- Shenandoah 的转发指针是“按需激活”,而 Brooks 是“始终生效”,后者带来稳定但不可忽略的性能税
实际效果:停顿时间与吞吐的平衡
转发指针让 Shenandoah 能把绝大部分移动工作(标记、复制、引用更新)移到并发阶段完成,最终 STW 仅保留在初始标记和最终更新根集合两个极短环节。
- 典型场景下,99% 的 GC 停顿可压至 10ms 以内,且基本不随堆大小增长
- 代价是轻微的吞吐下降(约 5–10%,主要来自读屏障插入和对象头额外存储)
- 适合对延迟敏感、堆较大(数十GB)、且能接受小幅吞吐折损的服务,比如实时风控、网关、低延迟交易中间件










