- 年更博主,前来报到🐶。最近发生了一个频繁oom的问题,经过排查和zgc有点关系。zgc之前没有怎么接触过,借此机会也研究了下。(具体原因就没法在这写了)
- 这篇文章更多的是ZGC资料的汇总,方便快速回顾。
- 英语水平有限,外加JDK21后出现分代特性,文章较少,如有错误,欢迎指出。参考内容在末尾!!!
简介
Z Garbage Collector(ZGC):ZGC是一个可拓展的低延迟的垃圾收集器。ZGC可以在不停止应用程序线程执行超过一毫秒的情况下,并发完成所有耗时的工作。ZGC可以很好地支持从几百兆字节到 16TB 的堆大小。
ZGC从JDK 11 引入,JDK 15开始可以用于生产环境,JDK 21被重新实现以支持分代。
ZGC垃圾回收过程
以上是美团整理的垃圾回收过程。
ZGC只有三个STW阶段:初始标记,再标记,初始转移。
- 初始标记和初始转移分别都只需要扫描所有GC Roots,其处理时间和GC Roots的数量成正比,一般情况耗时非常短;
- 再标记阶段STW时间很短,最多1ms,超过1ms则再次进入并发标记阶段。
Mark Phase
这个不分大致分为3个阶段
- 初始标记:STW 阶段,从GC Roots 标记他们可以访问到的对象。
- 并发标记:上一个阶段STW阶段结束后,会并发遍历、标记
- 再标记:遍历完成后,会有一个最后的、非常短暂的STW,用于处理一些边界情况,随后标记过程就结束了。
Relocate Phase
并发转移准备:ZGC将堆划分为多个页面(page),并在该阶段开始时并发地选择一组页面--有存活对象且需要被重新分配。
初始转移:STW,在此期间ZGC会转移GC Root,将它们的引用重新映射到新的地址。
并发转移:此阶段是并发的,这就有一个问题,如果用户线程访问到转移后的对象怎么办,这个就是通过加载屏障(Load Barrier)来实现的。
着色指针(Pointer colouring)

着色指针:核心思想是空间换时间。指针大小为64位,其中内存地址是42为,其他22位可以用来存储信息。目前用了以下4位
- Finalizable:是否可以被回收(JDK18启用)
- Remapped: 表示该引用需要被重定向
- Marked1&Marked0:是否被GC标记,每个GC周期会交替使用这2个标记
加载屏障(Load Barrier)
承接上文说的,如果在并发转移(Relocation)的过程中,用户线程访问到了被转移的对象,此时加载屏障(Load Barrier)开始介入,保证引用始终指向有效对象。
- 先判断标记位------Remapped 检查引用指针的高位是否设置了Remapped标志位。
如果设置了,表示该对象可能已经被转移但引用尚未更新,需要更新引用。
- 判断是否已经被转移 加载旧地址的对象头,检查是否是在 Forwarding Stub(转发表)中。 如果存在,说明对象已经迁移,引用需要被更新。 如果不存在,什么也不做,保留引用。

存储屏障
这个是分代ZGC特有,主要是用来跟踪跨代引用。ZGC在着色指针中添加标记位,以便存储存储屏障能够判断是否包含跨代指针。
其他特性
- 无多重映射内存(No multi-mapped memory)
- 将非分代zgc的多重映射内存取消了,更好的检测堆内存使用量(实际使用量3倍的问题)
- 优化屏障(Optimized barriers)
- Fast paths and slow paths
- Minimizing load barrier responsibilities
- Remembered-set barriers
- SATB marking barriers
- Fused store barrier checks
- Store barrier buffers
- Barrier patching
- 双重缓冲记忆集合(Double-buffered remembered sets)
- 使用位图代替卡表(512位,每一位标记一段区域是否被写过)形式
- 一个位图(bitmap)用于活跃线程标记,另一个用于gc
- 每次young gc,交换2个位图,gc处理老位图
- 重新分配无需额外堆内存(Relocations without additional heap memory)
- 2阶段gc,第一阶段标记可达对象,二阶段分region重定位
- 密集的堆区域(Dense heap regions)
- region使得对象更密集,zgc会分析年轻代region的密度,判断哪些更值得清理。不需要清理的区域直接接地老化
- 大对象(Large objects)
- ZGC 已经能够很好地处理大型对象。通过将虚拟内存与物理内存解耦,并预留过多的虚拟内存,ZGC 通常可以避免使用 G1 时导致大型对象分配困难的碎片问题。
- 完整的垃圾收集(Full garbage collections)
- 如果 GC 是由显式调用(如 System.gc())触发的,ZGC 会在旧生代 GC 前执行一次额外的年轻代 GC,并强制将存活对象提升到旧生代,确保引用处理和类卸载能正常发生。
分代ZGC
分代ZGC JDK21 发布的新特性,旨在降低分配停滞(allocations stalls)的风险,减少所需的堆内存开销,并降低垃圾收集的CPU开销。与非分代 ZGC 相比,这些优势预计不会显著降低吞吐量。非分代 ZGC 的基本特性(例如,暂停时间不超过 1 毫秒,以及支持从几百兆字节到数 TB 的堆大小)将得以保留。
分代 ZGC 基于弱分代假说,即年轻对象往往会在年轻时死去,而老对象往往会保留下来。通过更频繁地收集年轻对象,ZGC 可以提高应用程序的性能。
分代 ZGC 引入了多个使其不同于非分代 ZGC 和其他垃圾收集器的设计概念,包括无多重映射内存、优化屏障、双缓冲记忆集、无追加堆内存重定位、密集堆区域、大对象和完整垃圾收集。
分代 ZGC 的引入可以大幅提升在 Java 平台上运行应用程序的性能。通过更频繁地收集年轻对象,分代 ZGC 可以带来更低的延迟、更少的内存开销和更高的 CPU 利用率。这使得它在大多数用例中都成为比非分代 ZGC 更好的解决方案。
JDK24移除了非分代模式。
内存归还问题
- commit 内存: 向操作系统申请并保留的物理内存,jvm随时使用
- uncommit 内存: jvm预留了,但是没有实际占用。操作系统可以拿来用
zgc默认情况下是uncommit 内存,可以使用 -XX:-ZUncommit
禁用这个特性,如果-Xmx 和-Xms设置的一样,也会被隐式禁用。
可以配置 -XX:ZUncommitDelay=(默认300s),设置延迟uncommit 内存的延迟时间
但是此处很奇怪,当系统rss大道limit的时候,且xmx=xms,再监控上,我观察到,jvm释放了一部分内存,暂时没有找到任何文献,有知道的欢迎指教。
ZGC相关调优参数
通用调参
参数 | 注释 |
---|---|
-Xms |
最小堆大小 |
-Xmx |
最大堆大小 |
-XX:SoftMaxHeapSize |
ZGC 的软上限,GC 会尽量控制堆不超过这个值(非硬限制) |
-XX:ConcGCThreads |
并发 GC 阶段使用的线程数(如并发标记) |
-XX:ParallelGCThreads |
Stop-The-World 阶段使用的并行线程数(如初始标记) |
-XX:+UseDynamicNumberOfGCThreads |
根据系统负载动态调整 GC 线程数 |
-XX:+UseLargePages |
启用大页内存(HugePages,手动配置) |
-XX:+UseTransparentHugePages |
启用透明大页(由操作系统自动分配) |
-XX:+UseNUMA |
在 NUMA 架构下启用内存亲和性优化 |
-XX:SoftRefLRUPolicyMSPerMB |
每 MB 空闲堆内存对应的 SoftReference 最小保留时间(毫秒) |
-XX:AllocateHeapAt=<路径> |
将堆内存分配到指定挂载路径(如持久内存或大页挂载点) |
ZGC特有
参数 | 注释 |
---|---|
-XX:ZAllocationSpikeTolerance=<百分比> |
忍受内存分配突增的阈值,超过该值可能触发 GC,默认约 2% |
-XX:ZCollectionInterval=<秒> |
主动 GC 的最小间隔时间(节流用) |
-XX:ZFragmentationLimit=<百分比> |
堆碎片率超过该阈值时触发 GC,默认 25% |
-XX:ZMarkStackSpaceLimit=<字节> |
标记阶段内部栈最多可使用的内存大小 |
-XX:+ZProactive |
启用主动 GC,即使没有明显内存压力也会周期收集 |
-XX:+ZUncommit / -XX:-ZUncommit |
开启/关闭自动释放未使用堆内存(返还给操作系统) |
-XX:ZUncommitDelay=<秒> |
堆内存空闲多久后可被释放,默认 300 秒 |
ZGC诊断
参数 | 注释 |
---|---|
-XX:ZStatisticsInterval=<秒> |
设置 GC 统计信息的输出间隔,默认 10 秒 |
-XX:+ZVerifyForwarding |
验证对象转发指针的正确性(调试用) |
-XX:+ZVerifyMarking |
验证标记阶段是否完整(调试用) |
-XX:+ZVerifyObjects |
验证堆中对象的结构完整性(调试用) |
-XX:+ZVerifyRoots |
验证 GC 根集合的准确性(调试用) |
-XX:+ZVerifyViews |
验证 ZGC 内部视图映射关系是否正确(调试用) |
-XX:ZYoungGCThreads |
年轻代 GC 所使用的线程数(仅 ZGC 有效) |
-XX:ZOldGCThreads |
老年代 GC 所使用的线程数(仅 ZGC 有效) |
-XX:+ZBufferStoreBarriers |
启用写屏障缓冲(用于优化写屏障性能) |
参考文献