JDK JDK 学习手册8 → 27 · JDK 专用学习站
DEEPDIVE · GC · JEP 333 (JDK 11, 实验性)

ZGC:着色指针、读屏障与并发重定位

JDK 11 · 深钻主题 02 / 03 · 预计 50 分钟
进阶四步深钻JEP 333
一句话ZGC 用着色指针把标记状态放进引用本身,用读屏障把标记、重映射和重定位的工作摊到应用线程,从而把停顿压到毫秒级。JDK 11 的它是实验特性,只支持 Linux/x64,单代、无压缩 oops、不支持类卸载。理解这些边界,比记住「低延迟」三个字重要。
本质:ZGC 的核心不是「更快的 GC」,而是「把 GC 工作从暂停里搬到并发里」:指针带颜色,读屏障负责修正,重定位靠转发指针,暂停只处理根集合。

学完你要能自己回答

  1. 着色指针把哪些状态放进了引用?为什么要多重映射?
  2. 读屏障的快路径和慢路径分别做什么?
  3. 并发重定位如何找到对象的新地址?什么时候重映射?
  4. JDK 11 的 ZGC 有哪些硬限制,什么时候不该选它?
01 先看它要解决什么问题02 着色指针:把状态放进地址位03 一次 ZGC 周期只有三个暂停点

先看它要解决什么问题01

JDK 11 的默认 GC 是 G1。G1 已经把大部分标记和清理做成并发,但 Young GC 和 Mixed GC 仍需暂停;堆越大、存活对象越多,暂停越难稳定压在毫秒级。ZGC 的目标是让暂停时间不再随堆大小和存活对象数量线性增长。

维度G1(11 默认)ZGC(11 实验)
堆范围小到中大8MB–4TB
暂停目标可配置,默认 200ms不超过 10ms
并发整理部分阶段并发标记与重定位都并发
分代有 Young / Old单代
压缩 oops支持不支持
类卸载支持11 不支持
平台全平台仅 Linux/x64

JDK 11 的 ZGC 是实验特性(JEP 333),不是默认 GC,也不能在 Windows/macOS 上启动。它解决的是大堆低延迟,不是所有场景的吞吐。

「暂停不超过 10ms」是设计目标,不是硬实时保证。分配速率过高、堆不足时仍会出现分配停顿;ZGC 11 还是单代,短命对象也要经历完整周期。

着色指针:把状态放进地址位02

传统 GC 用对象头或额外的标记位图记录「是否已标记」「是否已移动」。ZGC 换了个位置:把 64 位引用中的 4 位拿来做颜色标记,剩下的位表示对象地址。

颜色位含义
marked0在第一次标记视图中已标记
marked1在第二次标记视图中已标记
remapped引用已指向对象当前位置
finalizable对象处于 finalization 相关状态

同一份堆内存在虚拟地址空间中被映射三次,分别对应 marked0、marked1、remapped 三个视图。给指针「染色」只需要换一个视图,不需要改物理地址;物理内存仍然只有一份。

项目JDK 11 的实现
地址位42 位,最大 4TB 堆
颜色位4 位
视图marked0 / marked1 / remapped 三重映射
指针宽度始终 64 位,不支持压缩 oops
颜色位占用了地址位,所以 ZGC 11 不能启用压缩 oops。堆指针始终是 64 位,对象引用占用的内存比开启压缩 oops 的 G1 更多。

一次 ZGC 周期只有三个暂停点03

ZGC 把一次回收拆成「暂停做根、并发做全堆」:标记和重定位的绝大部分工作与应用线程同时运行,STW 只保留三个很短的阶段。

  1. Pause Mark Start:暂停,扫描 GC roots,确定初始存活集合。
  2. Concurrent Mark / Remap:并发标记对象,同时重映射上一轮遗留的引用。
  3. Pause Mark End:暂停,处理弱引用、JNI 等边界,完成标记。
  4. Concurrent Prepare for Relocate:并发统计页存活率,选择重定位集。
  5. Pause Relocate Start:暂停,重定位根集合中的对象。
  6. Concurrent Relocate:并发复制剩余存活对象,修正引用。

Mark Start 和 Relocate Start 都需要扫描根集合,所以根越多、线程栈越深,这两个暂停越长。ZGC 把「堆越大暂停越长」变成了「根越多暂停越长」。

ZGC 11 是单代:没有 Young/Old 之分,所有对象在同一个周期里处理。周期模型简单,代价是短命对象也要被标记;分配速率高时更容易出现分配停顿。

01 读屏障:每次加载引用都过一道检查02 并发重定位:转发指针与惰性重映射03 ZPage:分配、回收与内存管理

读屏障:每次加载引用都过一道检查01

读屏障(load barrier)插在每次从堆中加载引用之后。应用线程拿到一个引用时,先检查它的颜色位:颜色与当前阶段匹配就走快路径;不匹配就进入慢路径,由慢路径完成标记、跟随转发指针或重映射。

路径触发条件动作
快路径引用颜色与当前阶段匹配直接返回,只有少量指令
慢路径未标记 / 待重映射 / 待重定位标记对象、跟随转发指针、修正引用

读屏障的关键作用是让「修正引用」可以延迟:应用线程什么时候读到旧引用,就什么时候把它修正到新地址。这样重定位不必暂停整个世界,只需在根修正时短暂 STW。

代价也很直接:每次引用加载都多了一次颜色检查。ZGC 用吞吐和 CPU 换低暂停,读屏障就是这个交换的落点。

ZGC 11 以读屏障为核心。不要把读屏障理解成「每次访问都加锁」;快路径通常只是几条比较和分支指令,慢路径才会进入复杂的修正逻辑。

并发重定位:转发指针与惰性重映射02

重定位把存活对象从 From 页复制到 To 页。复制完成后,ZGC 在 From 页的原对象里留下转发指针;之后任何线程通过旧引用访问,读屏障都会跟随转发指针到新地址,并把引用改写成新地址。

  1. 选择重定位集:按页统计存活率,优先回收垃圾比例高的页。
  2. 复制对象:并发进行,应用线程可以继续分配和访问。
  3. 安装转发指针:旧地址保留转发信息,直到所有引用被修正。
  4. 惰性重映射:读屏障在访问时修正引用;下一个标记周期会把剩余引用全部重映射。

转发指针放在 From 空间的对象里,而不是额外的全局表。查找新地址只需要一次内存访问,但 From 页必须等所有引用都重映射后才能释放。

「并发重定位」不等于「没有暂停」:根集合的重定位仍在 Relocate Start 的 STW 暂停里完成。ZGC 把暂停压到根扫描和根修正,而不是完全消除暂停。

ZPage:分配、回收与内存管理03

ZGC 的堆由 ZPage 组成,按对象大小分为小、中、大三档。页既是分配单位,也是重定位和回收的单位。

页类型大小用途
Small2MB小对象
Medium32MB中等对象
Large2MB 的整数倍大对象,独占页

回收的页进入页缓存,按大小分类保存,供后续分配复用;缓存过大时再归还给操作系统。ZGC 还会按 NUMA 节点组织页,尽量让对象分配在访问它的线程附近。

  • 11 不支持类卸载:类元数据不会被 ZGC 回收,频繁生成类的应用要单独评估 Metaspace。
  • 11 不支持压缩 oops:对象引用固定 64 位,堆内引用和缓存占用更高。
  • 11 是单代:没有年轻代/老年代,也没有跨代引用处理。
页缓存不是越大越好。它减少向操作系统申请内存的次数,但也会让进程的 RSS 看起来比实际存活对象多;排查内存占用时要把页缓存算进去。
01 在 JDK 11 打开 ZGC02 看什么日志,怎么判断03 什么时候用,什么时候别用

在 JDK 11 打开 ZGC01

JDK 11 的 ZGC 是实验选项,必须先解锁实验选项,再选择 GC。堆大小和日志参数与 G1 类似。

java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -Xmx16g -Xlog:gc* App

本机验证环境是 Windows,JDK 11 的 ZGC 只支持 Linux/x64,所以上面的命令无法在本机执行。不加解锁选项时,JVM 先拒绝实验选项:

java -XX:+UseZGC -version
Error: VM option 'UseZGC' is experimental and must be enabled via -XX:+UnlockExperimentalVMOptions.
Error: The unlock option must precede 'UseZGC'.
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.

加上解锁选项后,才会暴露真正的平台限制:

java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -version
Error occurred during initialization of VM
Option -XX:+UseZGC not supported
第一条错误是选项顺序问题:-XX:+UnlockExperimentalVMOptions 必须放在 -XX:+UseZGC 之前。第二条错误才是平台不支持。不要把 Windows 上的失败误判为参数写错。

看什么日志,怎么判断02

ZGC 的日志同样走统一日志框架。重点看 Pause 行、堆前后变化、分配停顿和页缓存,而不是只盯总吞吐。

观察项日志 / 命令判断
STW 暂停-Xlog:gc* 中的 Pause 行是否稳定在目标范围内
堆占用-Xlog:gc 的 before->after是否存在持续上涨
分配停顿日志中的 Allocation Stall堆太小或分配速率过高
页缓存-Xlog:gc+heap=debug缓存是否长期不归还
堆信息jcmd <pid> GC.heap_info确认页与堆的实际占用

Linux 上可以把日志按大小滚动,避免长时间运行写满磁盘:

java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC \
     -Xmx16g -Xlog:gc*:file=zgc.log:time,uptime:filecount=5,filesize=20M \
     -jar app.jar
不要伪造 ZGC 日志。本机 Windows 无法启动 ZGC,因此没有 ZGC 运行日志可贴;上面只核对了参数与阶段名称。需要真实毫秒数时,必须在 Linux/x64 的 JDK 11 上运行。

什么时候用,什么时候别用03

ZGC 适合堆大、停顿敏感、吞吐可以让步的服务;不适合追求极致吞吐、堆很小、或依赖类卸载的场景。

场景建议原因
常规 Web 服务先用 G1默认、全平台、分代、支持类卸载
32GB 以上大堆、停顿敏感评估 ZGC暂停与堆大小解耦,代价是读屏障
Linux/x64 以外的平台不能用 ZGCJDK 11 只支持 Linux/x64
频繁生成类慎用 ZGC 1111 不支持类卸载,Metaspace 可能持续上涨
性能测试、短生命周期可考虑 Epsilon不回收,堆满即失败
  • 选 G1:绝大多数 11 应用的默认答案,尤其是堆不大、依赖类卸载或压缩 oops 的场景。
  • 选 ZGC:大堆、低延迟、Linux/x64,且能接受读屏障开销与单代限制。
  • 选 Epsilon:只做性能测试或极短生命周期任务,明确知道不会 OOM;它不是「更快的 GC」。
ZGC 在 JDK 15 转正(JEP 377),JDK 21 才有分代版本(JEP 439),Windows/macOS 支持从 JDK 14 开始(JEP 364/365)。把 11 的 ZGC 当成生产默认选项是版本错位。
01 先猜再看:四个常见误解02 费曼自检:能否讲清一条引用的一生

先猜再看:四个常见误解01

先猜再看ZGC 是不是完全不用暂停?
不是。ZGC 仍有三个 STW 暂停点:Mark Start、Mark End、Relocate Start。它消除的是「随堆增大而增长的整理暂停」,不是所有暂停。
先猜再看颜色位是不是存在对象头里?
不是。ZGC 的颜色位在引用本身,不在对象头。对象头仍用于锁、哈希等;着色指针改变的是引用的编码方式。
先猜再看堆越大,ZGC 的暂停一定越长吗?
不一定。标记和重定位是并发的,堆大小不直接决定暂停;根集合大小、线程栈深度、分配速率和堆余量影响更大。
先猜再看JDK 11 的 ZGC 能用在 Windows 上吗?
不能。JDK 11 的 ZGC 只支持 Linux/x64;Windows/macOS 支持从 JDK 14 才开始。本机 Windows 上只能验证到启动被拒绝。

费曼自检:能否讲清一条引用的一生02

01. 一个引用从加载到被修正,读屏障做了什么?
加载后检查颜色位;颜色不匹配时进入慢路径,标记对象、跟随转发指针到新地址,并把引用改写为 remapped 状态。
02. 为什么 ZGC 需要三重映射?
marked0、marked1、remapped 三个视图对应同一份物理内存;切换颜色只需切换视图,不必修改地址或复制对象。
03. 并发重定位期间,应用线程访问旧地址会怎样?
读屏障发现旧引用指向 From 页,读取其中的转发指针,转到 To 页的新对象,并顺手把引用修正。
04. ZGC 11 的三个硬限制是什么?
仅 Linux/x64、单代、不支持类卸载;此外还不支持压缩 oops。它们决定了 11 的 ZGC 只适合特定场景。
能把「指针颜色 → 读屏障 → 转发指针 → 惰性重映射」这条链讲清楚,并说清 11 的平台与分代限制,这一节就过关了。
学习进度 记录保存在本机浏览器 · 不上传

JDK 学习手册 · JDK / Java 专用学习站 · 纯静态 · 双击 index.html 即可使用

本站专注 JDK 版本演进与 Java 教学,非 Oracle / OpenJDK 官方站点。版本数据来自 OpenJDK 官方 Features 表;课程示例已在对应本机 JDK 实测。构建日期 2026-11-12。