ZGC的多重映射是将同一物理内存页映射到Marked0、Marked1、Remapped三个不同虚拟地址区间,配合读屏障实现并发转移与指针自愈,无需STW。

ZGC 的多重映射不是“映射同一物理变量内存”,而是将**同一段物理内存页**,同时映射到**多个不同的虚拟地址区间**(Marked0、Marked1、Remapped),让应用线程在不感知对象移动的前提下,通过读屏障自动跳转到新位置。它不作用于单个变量,也不改变变量语义,而是面向整个堆内存的底层地址管理机制。
为什么需要多重映射?
ZGC 要实现并发转移(Concurrent Relocation)——即 GC 线程在后台把对象从旧地址复制到新地址,而 Java 应用线程仍可正常读写这些对象,且完全无感。传统方式需 STW 或写屏障拦截所有写操作,开销大;ZGC 换了一条路:靠硬件支持的虚拟内存映射 + 读屏障自愈。
关键点在于:对象被复制后,旧地址仍“有效”,只是内容已过期;新地址才是最新副本。多重映射让这两个地址都指向同一份物理内存(复制过程中)或各自独立的物理页(复制完成后),再由读屏障决定该读哪边。
三个视图如何共用物理内存?
ZGC 在启动时通过 mmap() 系统调用,为每块物理内存页建立三组虚拟地址映射:
- Marked0 视图:当前活跃的标记视图,指向对象当前所在物理页;用于本轮并发标记和后续转移的源地址。
- Marked1 视图:备用标记视图,与 Marked0 交替使用,避免标记状态混淆;初始也映射到同一物理页,但逻辑上不参与当前轮次访问。
- Remapped 视图:表示“已完成转移”的稳定视图,指向对象迁移后的新物理页;GC 完成复制后,会把该对象在 Remapped 视图中的对应虚拟地址激活。
注意:这三个虚拟地址范围互不重叠,但操作系统允许它们映射到相同或不同物理页帧。例如,一个刚分配的对象,其 Marked0、Marked1、Remapped 地址都映射到同一个刚申请的物理页;而一个已被转移的对象,Marked0 地址可能映射到“旧页”(即将回收),Remapped 地址则映射到“新页”(已就绪)。
读屏障如何配合多重映射完成自愈?
当应用线程通过某个虚拟地址(比如 Marked0 中的指针)加载对象字段时,JVM 插入的读屏障会检查该指针是否“已重定位”:
- 若未重定位:直接读取,返回原物理页数据;
- 若已重定位(即该对象已在 Remapped 视图中有了新副本):读屏障自动将访问重定向到 Remapped 视图下的对应地址,并更新该指针值(染色指针中带重定位标志位)。
这个过程对 Java 代码完全透明。所谓“自愈”,就是指哪怕你还在用旧地址读,系统也能当场给你新数据,并悄悄帮你升级指针——前提是 Marked0 和 Remapped 都能通过 mmap 正确映射到各自目标物理页,而这正是多重映射提供的基础设施。
它和操作系统 mmap 的关系
ZGC 不自己管理物理页搬运,而是复用 Linux/Windows 的虚拟内存机制:
- 每个 ZGC 堆页面(通常 2MB)在初始化时,用 mmap(MAP_FIXED) 三次,分别注册到 Marked0、Marked1、Remapped 的虚拟地址空间;
- 每次 mmap 都指定同一块物理内存(初期)或不同物理内存(转移后),由 GC 控制器动态调整页表项;
- MMU(内存管理单元)在 CPU 访存时自动完成虚拟→物理翻译,ZGC 只需修改页表,无需碰应用线程的寄存器或栈帧。
这种设计把“地址切换”的成本从软件层卸载到硬件 MMU,是 ZGC 实现亚毫秒停顿的关键支点之一。

















