ZGC并发重映射不修改指针值,而是通过读屏障检测Remapped位+三视图页表切换实现按需自愈:未置位则查转发表并写回新地址,已置位则零开销硬件寻址,全程无STW。

ZGC 的并发重映射(Remap)阶段不修改指针本身,而是靠读屏障 + 多重映射协同完成引用跳转,整个过程对应用代码完全无感。
指针自带状态,访问即触发修复
每个 Java 引用(变量)在 ZGC 中实际是一个 64 位染色指针,高几位编码运行时状态。其中第 46 位是 Remapped 位,直接表明该指针是否已指向重定位后的新地址:
- 若 Remapped 位未置位,说明对象尚未重定位,或重定位刚完成但该引用还没更新——此时读屏障会查转发表(Forwarding Table),拿到新地址,把指针“就地”写回(自愈),并置位 Remapped 位
- 若 Remapped 位已置位,说明该引用已是新地址,后续访问直接走硬件寻址,零额外开销
- 这个修复动作只发生在变量首次被读取时,不是批量扫描,也不是 STW 统一改,是“按需懒更新”
三视图内存映射保障新旧共存
ZGC 利用操作系统 mmap 能力,为同一物理页建立三个虚拟地址视图:Marked0、Marked1 和 Remapped。变量始终持有某个视图下的虚拟地址(比如原 Marked0 地址),而底层页表可动态切换其映射目标:
- 重分配前:Marked0 视图映射到物理页 A,所有引用都指向这里
- 重分配中:对象复制到物理页 B,Remapped 视图也映射到页 B;Marked0 视图仍映射页 A,确保旧引用不会崩溃
- 切换靠页表项更新(TLB 刷新),不是改指针值——变量继续用原地址访问,硬件自动路由到新物理页
Remap 阶段的日志与隐性开销
日志里出现 Concurrent Remap (n of m) 并不表示在批量改指针,而是 ZGC 正在并发扫描线程栈,识别哪些栈帧里还藏着未更新的旧引用:
- 每次扫描一个线程栈的小批次,耗时异常(如卡住几毫秒)通常源于栈过深、Lambda 表达式嵌套多、或匿名内部类频繁生成局部引用
- 若连续多次 Remap 耗时 > 3ms,且
ZStatistics显示 “Threads scanned” 数远超实际线程数,说明某些栈被反复扫描——常见于闭包捕获大量对象又未及时释放的场景 - 它不阻塞业务线程,但 CPU 时间片消耗真实存在,是当前 ZGC 唯一需遍历栈的并发阶段

















