标记-整理算法必须同步更新三类引用:栈帧局部变量、静态变量与常量池引用、其他存活对象内部字段;采用三轮扫描(标记并预计算地址→更新所有引用→移动对象)和forwarding指针保障映射准确,全程需STW确保安全性。

标记-整理算法在移动存活对象后,必须同步更新所有指向这些对象的引用地址,否则程序会因访问旧地址而崩溃。这不是可选步骤,而是保证内存安全的强制环节。
哪些引用必须更新
三类关键引用源必须全覆盖扫描并修正:
- 栈帧中的局部变量:当前线程执行中可能正持有某个对象的引用,比如方法调用时传入的参数或中间计算结果
- 静态变量与常量池引用:类加载后长期驻留的方法区里,static字段、final常量等都可能存着对象地址
- 其他存活对象内部的字段:例如 ArrayList 实例的 elementData 数组、HashMap 的 Node 链表节点、对象嵌套关系中的任意字段引用
更新不是“边移边改”,而是分阶段预计算
为避免竞态和数据错乱,JVM 不在移动过程中动态更新指针,而是采用三轮扫描机制:
- 第一轮仅标记,并为每个存活对象预先算好目标地址(如从堆起始地址开始累加偏移)
- 第二轮遍历所有引用源(栈、静态区、堆中存活对象字段),把原引用值替换成对应的新地址
- 第三轮才真正复制对象数据到新位置,同时清除标记位
这种分离设计确保了引用更新总能查到准确映射,不会因对象尚未移动或已覆盖旧内容而失效。
更新依赖 forwarding 指针实现映射保障
在正式移动前,每个存活对象会先设置一个 forwarding 字段,记录它将要搬去的新地址。这个字段就像一张“搬家通知单”:
- 当 A 对象引用 B 对象时,更新阶段通过 B 的 forwarding 找到 B’ 地址,再把 A 中的引用字段写成 B’
- 即使多个对象相互引用(如链表、树结构),只要 forwarding 表完整,就能逐层推导出全部新地址
- forwarding 是临时辅助结构,移动完成后即被丢弃,不占用长期内存
为什么必须 Stop-The-World
用户线程一旦运行,就可能读取未更新的旧引用、写入正在迁移的对象、或触发新的 GC Roots 变化。只有暂停全部应用线程,才能确保:
- 引用扫描过程看到的是稳定快照
- 更新后的指针不会被运行中的代码覆盖或误用
- 对象移动期间,不会有线程试图访问其旧地址或中间状态
这是安全性与正确性的底线要求,无法通过并发技巧绕过。

















