ZGC
约 1886 字大约 6 分钟
2026-05-24
参考了6篇结果 ZGC(Z Garbage Collector)是 Java 生态中一款具有里程碑意义的低延迟垃圾收集器。它的设计目标非常激进:在任意堆内存大小下(从几百 MB 到 TB 级),将 GC 停顿时间控制在 10 毫秒以内。 为了帮你彻底理解 ZGC,我将从设计思想、核心技术原理、迭代演进历史以及优缺点这几个维度为你进行全面梳理。
- 设计思想:为“低延迟”而生 ZGC 的核心设计理念可以概括为三个关键词:并发(Concurrent)、可扩展(Scalable)、无分代(Non-Generational)。 极致的并发性: 传统垃圾收集器(如 G1、CMS)虽然部分阶段是并发的,但依然存在“初始标记”、“重新标记”等 Stop-The-World(STW)阶段,且这些阶段的耗时会随着堆内存增大而线性增长。ZGC 的目标是将所有耗时最长的操作(如标记、转移/整理)都做成与应用线程并发执行,从而消除长时间的停顿。 堆大小无关性: ZGC 的停顿时间主要取决于 GC Roots 的数量(如线程栈深度、全局变量等),而与堆中活跃对象的数量或堆的总大小无关。这意味着无论你的堆是 4GB 还是 16TB,停顿时间几乎是一样的(亚毫秒级)。 简化内存管理: ZGC 采用了不分代的内存布局(JDK 21 之前)。它不区分年轻代和老年代,整个堆是一整块统一管理的区域。这虽然牺牲了部分针对短期对象的回收效率,但极大地简化了设计,避免了分代收集器中复杂的跨代引用处理(Remembered Set)带来的开销。
- 核心技术原理:它是如何做到的? ZGC 能够实现低延迟,完全依赖于两项颠覆性的核心技术:染色指针(Colored Pointers)和读屏障(Load Barriers)。 🎨 染色指针(Colored Pointers) 这是 ZGC 的基石,也是它与 G1 最大的区别。 传统做法: 垃圾收集器通常在对象头(Object Header)中存储标记信息(如是否被标记、是否需要清理)。 ZGC 做法: ZGC 将这些标记信息(元数据)直接存储在指针本身中。 在 64 位系统中,Linux 通常只使用 48 位或 52 位进行寻址,剩余的高位是空闲的。 ZGC 利用指针的高 4 位来存储 4 个标志位(Marked0, Marked1, Remapped, Finalizable)。 优势: 这样做使得 ZGC 在访问对象状态时,不需要访问对象头,直接通过指针位运算即可获取状态,速度极快。同时,这为“指针自愈”提供了基础。 🚧 读屏障(Load Barriers) 读屏障是 JVM 在应用程序读取对象引用时插入的一小段代码。 作用: 当应用线程通过指针访问对象时,JVM 会触发读屏障。 自愈机制: 读屏障会检查指针的“颜色”。如果发现对象处于“被移动”状态(即旧地址),读屏障会自动将对象复制到新地址(或者通过转发指针找到新地址),并将指针更新为指向新地址(Remapped 状态)。 结果: 应用线程永远看到的是正确的对象引用,而对象的移动和引用更新是在并发过程中由读屏障透明处理的,无需 STW。
- ZGC 的迭代演进历史 ZGC 的发展经历了从“实验性”到“生产可用”,再到“功能增强”的过程。以下是其关键节点: 表格 JDK 版本 状态 关键特性与说明 JDK 11 引入 ZGC 首次亮相。这是一个实验性的功能,需要通过 -XX:+UnlockExperimentalVMOptions 参数开启。主要支持 Linux/x64 平台。 JDK 13 改进 支持大内存页(Large Page),进一步降低 TLB(转换检测缓冲区)缺失率,提升性能。同时支持取消提交未使用的内存(Return Unused Memory)。 JDK 14 扩展 移除了 -XX:+UnlockExperimentalVMOptions 参数依赖,但仍需 -XX:+UseZGC 显式启用。 JDK 15 正式 ZGC 从“实验性”转为正式特性(Production Ready),不再需要特殊参数解锁。 JDK 16 跨平台 增加了对 macOS 和 Windows 系统的支持,不再局限于 Linux。 JDK 17 默认 JDK 17 LTS 版本中,ZGC 已经非常成熟稳定,成为许多低延迟场景的首选。 JDK 21+ 分代 ZGC 重大更新。JDK 21 引入了分代 ZGC(Generational ZGC)。这是为了解决纯不分代 ZGC 在处理大量短期对象时效率不如 G1 的问题。分代 ZGC 引入了年轻代概念,旨在保持低延迟的同时,进一步提升吞吐量和小对象回收效率。
- ZGC 的回收周期(工作流程) ZGC 的回收过程是一个并发的循环,主要包含以下几个阶段: 初始标记(Pause Mark Start): STW 阶段,非常短暂。扫描 GC Roots(线程栈、全局变量等),标记直接引用的对象。 并发标记(Concurrent Mark): 与应用线程并发执行。遍历对象图,通过染色指针标记存活对象。期间如果应用线程访问了未标记对象,读屏障会协助完成标记。 再标记(Pause Mark End): STW 阶段,处理并发标记期间的漏网之鱼(如弱引用处理),确保标记完成。 并发预备重分配(Concurrent Prepare for Relocate): 计算哪些 Region 的垃圾最多(回收收益最高),选定为“重分配集(Relocation Set)”。 初始转移(Pause Relocate Start): STW 阶段,为重分配集中的 Region 准备数据结构。 并发转移(Concurrent Relocate): 最核心阶段。将重分配集中存活的对象复制到新的 Region。如果应用线程此时访问了旧对象,读屏障会触发“指针自愈”,自动将对象复制到新位置并更新引用。 并发重映射(Concurrent Remap): 处理非强引用(软/弱引用)的更新。
- ZGC 的优缺点总结 ✅ 优点 停顿时间极短: 稳定在毫秒级甚至更低,且不随堆大小增长。 支持超大堆: 适合 TB 级内存的服务器应用。 无内存碎片: 通过并发整理(Compaction)解决了内存碎片问题,避免了 Full GC。 配置简单: 通常只需要设置 -Xmx,ZGC 会自动根据 CPU 和内存进行自适应调节。 ❌ 缺点 吞吐量略低: 由于使用了读屏障和染色指针,应用线程会承担一部分 GC 工作(写屏障/读屏障开销),导致吞吐量比 Parallel GC 或 G1 略低(通常在 10% 以内)。 占用更多 CPU: 并发执行意味着 GC 线程会一直占用 CPU 资源与应用线程竞争。 跨平台差异: 在 Windows/macOS 上,由于底层虚拟内存机制的限制,性能可能不如在 Linux 上极致。 💡 总结建议 如果你的应用是: 金融交易系统、实时游戏服务器、高并发微服务,且对延迟非常敏感(不能忍受几百毫秒的卡顿),那么 ZGC 是首选。 如果你的应用是: 离线数据处理、批处理任务,追求的是最大化吞吐量,对偶尔的 GC 停顿不敏感,那么 Parallel GC 或 G1 可能更适合。 随着 JDK 21 引入分代 ZGC,它的适用场景正在进一步扩大,未来很可能成为主流的通用收集器。