ZGC(Z Garbage Collector)是Java平台上一款革命性的低延迟垃圾收集器,由Oracle开发并逐步成为现代Java应用处理大内存堆和低延迟需求的首选解决方案。本文将全面剖析ZGC的核心原理、技术演进、关键特性以及实际调优策略,帮助开发者深入理解这一先进的垃圾收集技术。
ZGC最初作为实验性功能在JDK 11中引入(JEP 333),经过多个版本的迭代优化,到JDK 15时被宣布为生产就绪(Production Ready)。其设计目标是在任意堆内存大小下都能将垃圾收集停顿时间控制在10毫秒以内,同时尽可能减少对吞吐量的影响。
ZGC的发展历程体现了Java平台对低延迟垃圾收集的持续追求:
ZGC的名称中的"Z"并非缩写,而是向ZFS文件系统致敬,象征着这是一款革命性的、跨时代的产品。与传统的垃圾收集器不同,ZGC从设计之初就专注于解决大内存堆下的低延迟问题,采用了多项创新技术实现这一目标。
ZGC采用基于Region的堆内存布局,与G1收集器类似,但具有更高的动态性。在x64平台下,ZGC将堆划分为三类不同大小的Region:
这种设计使ZGC能够根据对象大小智能分配内存,既提高了小对象的分配效率,又避免了大对象造成的内存碎片。大型Region不会被重分配(因为复制大对象代价高昂),而中小型Region则可以根据回收策略动态创建和销毁。
在JDK 21之前,ZGC采用不分代的全堆收集策略,每次GC都需要扫描所有对象。基于"弱分代假说"(绝大多数对象朝生暮死)和"强分代假说"(老年对象难以死亡),JDK 21引入了分代ZGC(Generational ZGC)。
分代ZGC将堆划分为逻辑上的新生代和老年代(物理上不要求连续),并采用不同的回收策略:
分代模式通过更频繁地收集新生代对象,显著提高了资源利用率和性能。测试表明,分代ZGC不仅能保持亚毫秒级延迟,在很多情况下比非分代模式使用更少内存且吞吐量损失更小。
染色指针是ZGC最具标志性的技术创新,它将GC元数据直接存储在对象指针中,而非传统GC那样存储在对象头里。在64位Linux系统中,ZGC利用指针中高18位不能用于寻址的特性,使用其中4位作为标志位:
18位:预留给以后使用; 1位:Finalizable标识(与并发引用处理相关); 1位:Remapped标识(对象是否指向重分配集); 1位:Marked1标识; 1位:Marked0标识(用于辅助GC); 42位:对象地址(支持4TB内存)
染色指针技术带来了三大核心优势:
在分代ZGC中,染色指针布局进一步优化,将元数据放在指针低位,地址放在高位,使得通过单个移位指令(x64架构)就能检查指针状态并移除元数据。
ZGC是一款完全并发的收集器,所有繁重工作都与应用程序线程并行执行。为实现这点,ZGC采用了创新的屏障技术:
读屏障(Load Barrier):类似于AOP的前置通知,当读取处于重分配集的对象时,屏障会拦截并转发到新对象,同时更新引用值,这种"自愈"能力确保应用代码总是持有有效指针
存储屏障(Store Barrier):分代ZGC新增的屏障,负责无色指针到有色指针的转换、维护记忆集和标记存活对象
分代ZGC通过快慢路径分离优化屏障性能:快路径由JIT实现并直接插入编译代码;慢路径用C++实现处理复杂情况;还引入中间路径缓冲存储操作,只有当缓冲区满时才进入慢路径,大幅减少开销。
分代ZGC采用双重缓冲记忆集,由两个bitmap实现(非传统卡表):
这种设计消除了清除bitmap时的等待,也不需要额外内存屏障。对于并发标记期间引用变化导致的漏标问题,ZGC使用**SATB(Snapshot-At-The-Beginning)**算法解决,与G1收集器类似。
ZGC的完整收集周期可分为四个主要阶段,其中仅包含三个短暂的STW停顿:
并发标记(Concurrent Mark):
并发预备重分配(Concurrent Prepare for Relocate):
并发重分配(Concurrent Relocate):
并发重映射(Concurrent Remap):
分代ZGC的回收过程更精细,分为Minor GC和Major GC两种模式。Minor GC时初始标记的根集合包含:
ZGC设计哲学是简化调优,大多数情况下只需设置堆大小即可:
参数 | 作用 | 默认值 | 优化建议 |
---|---|---|---|
-Xms/-Xmx | 堆最小/最大大小 | 物理内存1/4 | 设为相同值避免堆震荡 |
-XX:+UseZGC | 启用ZGC | 关闭 | 低延迟场景必选 |
-XX:+ZGenerational | 启用分代模式(JDK21+) | JDK23默认开启 | 多数情况建议开启 |
-XX | 并发GC线程数 | CPU核心数12.5% | 内存压力大时可适当增加 |
-XX | 最小回收间隔 | 10秒 | 突发分配场景可延长 |
-XX:+UseLargePages | 启用大页内存 | 关闭 | 提升吞吐量和降低延迟 |
-XX:+UseNUMA | NUMA支持 | 开启 | 多插槽服务器保持开启 |
-XX:+ZUncommit | 返还闲置内存给OS | 开启 | 容器环境建议开启 |
ZGC的触发机制主要有三类:
常见的停顿原因包括:
堆大小设置:
大页内存配置:
bash# 配置Linux大页(需root)
echo 9216 > /proc/sys/vm/nr_hugepages
# 启用ZGC大页支持
-XX:+UseLargePages
大页(2MB)能减少TLB缺失,提升性能
NUMA优化:
监控与日志:
bash-Xlog:gc*=info:file=gc.log:time,tags:filecount=10,filesize=100m
分析GC日志关注:
特性 | ZGC | G1 | CMS | Parallel |
---|---|---|---|---|
设计目标 | 大堆低延迟 | 平衡延迟/吞吐 | 老年代低延迟 | 高吞吐量 |
最大暂停目标 | <10ms | 200ms左右 | 不可预测 | 无保证 |
堆大小支持 | 16TB+ | 4TB-32TB | 一般<4TB | 一般<4TB |
分代支持 | JDK21+支持 | 支持 | 支持 | 支持 |
压缩算法 | 并发压缩 | 部分压缩 | 不压缩 | 全压缩 |
内存屏障 | 读屏障+存储屏障 | 写屏障 | 写屏障 | 无 |
适用场景 | 大内存低延迟 | 平衡型应用 | 老年代低延迟 | 批处理作业 |
ZGC仍在持续演进,未来可能的发展方向包括:
实际使用中的挑战主要有:
ZGC代表了Java垃圾收集技术的最前沿,通过染色指针、并发处理和分代收集等创新技术,实现了大内存堆下的亚毫秒级停顿。从JDK11到JDK23的演进过程中,ZGC逐渐成熟并成为处理现代Java应用低延迟需求的优选方案。
对于开发者而言,理解ZGC的核心原理和调优方法,能够帮助我们在以下场景中获得最佳性能:
随着Java生态的持续发展,ZGC很可能会成为新一代Java应用的默认垃圾收集器,其设计理念和技术实现值得每一位Java开发者深入学习和掌握。