shenandoah 的“变量无感迁移”由 brooks pointers 与读屏障协同实现:对象头前设 8 字节原子转发指针,gc 复制后 cas 更新指向新地址,每次字段读取前经读屏障自动跳转,确保应用线程持续运行且语义不变。

Shenandoah 的“并发重映射”并非一个独立执行的强制阶段,而是一种可选的优化行为;它不承担语义正确性的兜底责任——真正保障对象引用始终访问到最新副本的,是 Brooks Pointers + 读屏障这一组合机制。
并发重映射只是“锦上添花”,不是“雪中送炭”
Shenandoah 在完成对象复制后,会进入一个名为 Concurrent Update References(并发更新引用)的阶段。这个阶段的目标是:主动扫描并修正那些仍指向旧地址的引用(比如堆中尚未更新的字段、静态变量、JNI全局引用等),把它们批量更新为新地址。
- 该阶段全程与用户线程并发运行,不阻塞应用
- 它能减少后续读屏障的触发次数,从而降低整体运行时开销
- 但它不是必需的:即使跳过或未完成该阶段,程序语义依然严格正确
原子性保障靠的是转发指针 + 读屏障,不是引用更新本身
每个对象头前插入的 Brooks Pointer 是一个原子可更新的引用字段(通常用 CAS 指令写入)。当 GC 线程将对象复制完毕,就以原子方式将该指针从指向自身改为指向新副本地址。此后,任何对该对象的读取操作都会经过读屏障:
- 读屏障检查该对象是否已转发(即 Brooks Pointer 是否已更新)
- 若已转发,自动跳转至新地址并返回结果
- 若尚未转发(比如 GC 还没处理到该对象),则直接读取原位置——此时仍是有效数据
这种设计让“引用是否更新完成”不再影响正确性:未更新的旧引用,靠读屏障兜底;已更新的引用,则直接命中新地址。用户线程无需等待、无需重试、无需感知迁移过程。
为什么不用强一致性更新所有引用?
强行同步更新全部引用(如 G1 的 Update RS 阶段)需要暂停线程或复杂协调,违背低延迟目标。Shenandoah 的取舍很明确:
- 用空间换时间:每个对象多占 8 字节,换来免 STW 的并发移动
- 用运行时开销换确定性:每次对象读取多一次间接跳转,换来 STW 时间恒定在毫秒级
- 用逻辑抽象换实现简洁:对象身份稳定,物理地址可漂移,JIT 和应用层完全无感
实际效果:应用层看不到“中间态”
假设某对象 A 正在被疏散,其旧地址为 0x1000,新地址为 0x2000:
- 栈中某个局部变量仍持 0x1000 → 读取时触发读屏障 → 查 Brooks Pointer → 跳转到 0x2000
- 堆中某字段仍存 0x1000 → 同样经读屏障自动重定向
- GC 线程原子更新 Brooks Pointer 后,所有后续访问立即生效,无竞态窗口
整个过程对 Java 代码透明,变量照常使用,对象照常传递,只是背后内存早已悄然重组。











