本文的 原始地址 ,传送门
尼恩说在前面
在40岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格,遇到很多很重要的面试题:
听说你是高手,说说,你的ZGC 怎么调优?
说说,ZGC 垃圾回收器的底层原理?
说说,ZGC 的浮动垃圾,是怎么处理的?
说说,ZGC 垃圾回收器的调优过程?
最近有小伙伴在面试 阿里,又遇到了相关的面试题。小伙伴懵了,因为没有遇到过,所以支支吾吾的说了几句,面试官不满意,面试挂了。
所以,尼恩给大家做一下系统化、体系化的梳理,使得大家内力猛增,可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”,然后实现”offer直提”。
当然,这道面试题,以及参考答案,也会收入咱们的 《尼恩Java面试宝典PDF》V171版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。
最新《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》的PDF,请关注本公众号【技术自由圈】获取,回复:领电子书
另外,此文的内容,作为 第14章,收入尼恩的《JVM 调优圣经》PDF。
尼恩三大 GC 学习圣经
第一大 gc 学习圣经:
第二大 gc 学习圣经:
接下来,咱们言归正传,开始讲 cms
第3大 gc 学习圣经:
ZGC解决了什么问题?
JVM 老大难问题: STW 卡顿(StopTheWorld)
对于Java的项目来说,JVM进行垃圾回收会有一个很大的问题,就是 STW (StopTheWorld)。
在很多业务场景中,STW时间太长是非常致命的,比如说手机系统 (Android ) 显示卡顿,通过对 GC 算法的不断演进,停顿时间控制在几个ms 级别;
再比如说一些实时证券交易系统以及一些大数据平台,大规模部署的情况下,STW太久会造成很严重的影响。
为了满足不同的业务需求,Java 的 GC 算法也在不停迭代,对于特定的应用,选择其最适合的 GC 算法,才能更高效的帮助业务实现其业务目标。
对于这些延迟敏感的应用来说,GC 停顿已经成为阻碍 Java 广泛应用的一大顽疾,需要更适合的 GC 算法以满足这些业务的需求。
首先,来看看大厂的 GC 垃圾回收器的 技术选型
主要 从 堆大小维度 进行技术选型
超小堆(小于 100M):这类场景下,Serial 串行收集器能有效减少额外开销,因为超小堆的垃圾回收任务本身不复杂,串行收集器按顺序执行回收操作,不会因多线程协调产生额外负担,能较好满足需求。
小堆1G - 4G 堆:可采用CMS 收集器。CMS 收集器以多线程并行方式进行垃圾回收,在这个堆大小范围内,能充分利用多核处理器性能,大幅提升垃圾回收效率,降低停顿时间。
中堆4G - 8G 堆:推荐使用 G1 收集器。G1 将堆划分为多个区域,能更精准地控制垃圾回收的范围和时间,在这个堆大小区间,其优势可有效平衡回收效率和停顿时间。
大堆8G - 16G 堆:推荐使用 G1 收集器。G1(Garbage-First)垃圾收集器在设计上旨在提供可预测的停顿时间,适用于大堆内存、低延迟需求的场景。对于8-16g内存的应用,G1可以通过设置-XX:MaxGCPauseMillis参数来控制最大停顿时间,通常可以将停顿时间控制在200ms以内。 1ms以内低延迟场景,在JDK 16之后,ZGC的停顿时间可以优化至1ms以内。对于8-16g内存的应用,如果对延迟要求极高,例如金融交易系统、实时数据分析等场景,ZGC是一个很好的选择。
超大堆(百 G 以上):ZGC 是最佳选择。超大堆下,CMS 或 G1 发生 Full GC 时停顿会达分钟级别,严重影响业务,而 ZGC 能将停顿时间控制在毫秒级,满足超大堆的业务需求。
三大 经典 GC的STW平均时间分析
以下是Parallel、CMS、G1三大GC的STW平均时间分析:
- Parallel GC:在年轻代回收时,STW时间较短,通常在几毫秒到几十毫秒之间,但在发生Full GC时,停顿时间会显著增加,可能达到几百毫秒甚至更长,因为它采用的是标记-整理算法,且在多线程环境下工作,需要暂停所有应用程序线程来完成回收操作。
- CMS GC:STW时间主要集中在初始标记和重新标记阶段。初始标记阶段的停顿时间很短,通常在几毫秒内,但重新标记阶段在高负载情况下可能会有较长的停顿,一般在几十毫秒到几百毫秒不等。
- G1 GC:STW时间受多种因素影响,包括年轻代回收、混合回收以及Full GC等。在年轻代回收时,停顿时间相对较短,通常在几十毫秒左右;混合回收阶段的停顿时间会比年轻代回收稍长,但一般也能控制在几百毫秒以内
超大堆 百 G / TB场景下STW 卡顿难题
近些年来,服务器的性能越来越强劲,各种应用可使用的堆内存也越来越大,常见的堆大小从 10G 到百 G 级别,部分机型甚至可以到达 TB 级别。
在这类大堆应用上,传统的 GC,如 CMS、G1 的停顿时间也跟随着堆大小的增长而同步增加,即堆大小指数级增长时,停顿时间也会指数级增长。
特别是当触发 Full GC 时,停顿可达分钟级别(百GB级别的堆), 传统的 GC,如 CMS、G1 的停顿时间 很长,以大型金融交易系统为例,运行在 64 核 CPU、512GB 内存的服务器上,因业务数据量巨大且交易频繁,对象分配和回收极为频繁,G1 收集器在执行 Full GC 时,需花费约 1 分钟来完成对全堆对象的处理,对业务的实时性造成显著影响。
超大堆 百 G / TB场景下, 当业务应用需要提供高服务级别协议(Service Level Agreement,SLA),例如 99.99% 的响应时间不能超过 100ms,此时 CMS、G1 等就无法满足业务的需求。
为满足当前应用对于 超低停顿、并应对大堆和超大堆 带来的挑战,伴随着 2018 年发布的 JDK 11,A Scalable Low-Latency Garbage Collector - ZGC 应运而生。

ZGC介绍
ZGC(The Z Garbage Collector)是JDK 11中推出的一款追求极致低延迟的垃圾收集器,它曾经设计目标包括:
- 支持TB量级的堆。我们生产环境的硬盘还没有上TB呢,这应该可以满足未来十年内,所有JAVA应用的需求了吧。
- 最大GC停顿时间不超10ms。目前一般线上环境运行良好的JAVA应用Minor GC停顿时间在10ms左右,Major GC一般都需要100ms以上(G1可以调节停顿时间,但是如果调的过低的话,反而会适得其反),之所以能做到这一点是因为它的停顿时间主要跟Root扫描有关,而Root数量和堆大小是没有任何关系的。
- 奠定未来GC特性的基础。
- 最糟糕的情况下吞吐量会降低15%。这都不是事,停顿时间足够优秀。至于吞吐量,通过扩容分分钟解决。
另外,Oracle官方提到了它最大的优点是:它的停顿时间不会随着堆的增大而增长!
也就是说,几十G堆的停顿时间是10ms以下,几百G甚至上T堆的停顿时间也是10ms以下。
ZGC(Z Garbage Collector) 是 Java 平台自 JDK 11 起引入的一款低延迟、可扩展的垃圾回收器,专为大堆内存(TB级)和亚毫秒级停顿场景设计。
其核心目标是通过完全并发操作,消除传统垃圾回收器(如 G1、CMS)在处理大堆内存时的长停顿问题,适用于对延迟极度敏感的实时系统(如金融交易、在线游戏、实时数据处理等)。
ZGC的优势
| 场景需求 | ZGC的优势 | 传统GC(如G1)对比 |
|---|---|---|
| 低延迟要求(<10ms) | 所有回收阶段并发执行,无STW停顿(仅转移阶段极短同步) | G1的Remark阶段需STW,停顿时间随堆增大而增加 |
| 超大堆内存(TB级) | 染色指针减少内存占用,并发处理无堆大小限制 | G1的卡表维护导致内存和CPU开销剧增 |
| 实时性敏感业务 | 支持亚毫秒级响应(如高频交易、游戏服务器) | 传统GC的长STW可能导致业务超时或中断 |
| 长期运行稳定性 | 无内存碎片风险,避免Full GC触发 | CMS可能因碎片触发Full GC,导致分钟级停顿 |
ZGC的劣势
CPU开销略高:
由于全并发操作依赖读屏障和染色指针,可能导致 5%~15% 的吞吐量下降(对比Parallel GC)。
早期版本兼容性限制:
JDK 15 前需 Linux x64 环境,后续版本逐步支持Windows/macOS和ARM架构。
无分代优化(JDK 21前):
JDK 21 引入 分代ZGC(ZGC Generational) 前,无法利用分代架构,去优化年轻代回收效率。
对吞吐量优先的场景,ZGC可能并不适合。
ZGC的设计目标是实现低延迟和高吞吐量,但在实际应用中,它在吞吐量优先的场景中可能并不适合。这是因为ZGC是单代垃圾回收器,每次处理的对象更多,更耗费CPU资源。此外,ZGC使用读屏障,读屏障操作需要耗费额外的计算资源。
例如,Zeus某离线集群原先使用CMS,升级ZGC后,系统吞吐量明显降低。
究其原因有二:
第一,ZGC是单代垃圾回收器,而CMS是分代垃圾回收器。单代垃圾回收器每次处理的对象更多,更耗费CPU资源;
第二,ZGC使用读屏障,读屏障操作需耗费额外的计算资源。
因此,对于吞吐量优先的场景,可能需要考虑其他垃圾回收器。
ZGC 的吞吐量损失 分析
为什么 ,对于吞吐量优先的场景,可能需要考虑其他垃圾回收器比如 CMS,而不是ZGC。
ZGC专注于 低延迟场景,在设计上需牺牲部分吞吐量。
根据实际生产环境测试,**CMS迁移至ZGC后吞吐量通常下降约10%-25%**。
例如:
- 美团风控服务从CMS切换至ZGC后,单节点吞吐量下降约18%,但延迟从40ms降至10ms以内 ;
- 某日志处理集群(堆内存64GB)升级ZGC后,任务处理速率(TPS)下降22%,主要因ZGC并发标记阶段占用额外CPU资源 。
ZGC 的核心影响因素与量化关系
-
堆内存规模:
中小堆(<32GB)下ZGC因内存管理开销更大,吞吐量降幅可达15%-25%;大堆(>100GB)场景因ZGC并发优势,降幅收窄至5%-10% ;
-
CPU资源竞争:ZGC的并发线程数(-XX)与业务线程争抢CPU,若集群CPU利用率原已超70%,吞吐量降幅可达20%以上 ;
-
对象分配模式:高频率短生命周期对象分配(如离线计算的中间数据)会加剧ZGC的染色指针维护开销,导致吞吐量额外损失5%-8% 。
ZGC 的优化建议与折中方案
- 吞吐量敏感场景:启用ZGC的分代模式(JDK21),可减少年轻代回收压力,预计提升吞吐量8%-12% ;
- 资源分配调优:通过-XX=4(限制并发线程)和-XX=N(N=CPU核数×0.5)平衡CPU争抢,可降低吞吐损失至10%以内;
- 混合部署策略:对延迟不敏感的离线计算模块保留CMS,仅对实时接口类任务启用ZGC,整体集群吞吐量损失可控制在5%以下。
老架构师 尼恩 大白话分析:为啥ZGC的“低延迟”和“吞吐量”会打架?
可以把垃圾回收(GC)想象成一个打扫房间的阿姨:
- ZGC 是个“轻手轻脚”的阿姨:她趁你工作的间隙(比如你喝水时)偷偷打扫,虽然你几乎感觉不到她停顿(低延迟),但她得一直轻手轻脚干活,打扫效率低(吞吐量低),整体打扫时间反而更长。
- Parallel/CMS 是个“大扫除”阿姨:她会突然喊你停下手头工作(STW),然后哐哐一顿猛扫,虽然打断你工作很烦(延迟高),但打扫得又快又干净(吞吐量高)。
ZGC的核心矛盾是:
它为了实现“随时能打断阿姨打扫”(低延迟),必须用更复杂的方式记录垃圾位置(比如染色指针),还要频繁检查哪里脏了。
这些额外操作就像让阿姨一边打扫一边写日记,当然干活更慢(吞吐量下降)。
老架构师尼恩,用大白话 进行 “低延迟”和“吞吐量” 介绍
两大核心指标的 大白话介绍
- 吞吐量 = 阿姨一天能打扫多少平米(干活的“总量”)
- 延迟 = 阿姨打扫时让你停下手头工作的最长时间(“打断你的程度”)
ZGC:
牺牲总量(吞吐量),保证不打断你(低延迟)
CMS:
允许偶尔大打断(高延迟),但总量更大(高吞吐量)
所以:
- 需要快速完成大量计算(离线任务)→ 选CMS(要总量)
- 需要随时响应请求(实时接口)→ 选ZGC(要不打断)
为啥离线计算用CMS,实时接口用ZGC?
- **离线计算(比如报表生成)**:
这类任务就像你周末在家加班,不怕阿姨突然大扫除打断你(延迟高无所谓),但希望阿姨一次打扫干净,别总磨蹭(高吞吐量优先)。CMS的“哐哐猛扫”模式更合适。
- **实时接口(比如支付系统)**:
这类任务就像你接客服电话,绝对不能容忍阿姨突然打断你说“稍等2分钟我扫个地”(延迟必须低)。哪怕阿姨干活慢点(吞吐量低),也要保证你随时能接电话。ZGC的“偷偷打扫”模式是刚需。
ZGC 的设计目标
超低延迟(亚毫秒级停顿)
ZGC 主打将垃圾回收的停顿时间控制在亚毫秒级(通常 < 10ms),尤其适用于对延迟敏感的大规模堆内存场景。其停顿时间不会因堆内存增大或活跃对象数量增加而显著上升13。
强可扩展性
支持从数百 MB 到 TB 级别的堆内存(未来规划扩展至 16TB),且堆内存规模扩大时,停顿时间几乎无增长。这种特性使其在大内存应用中表现稳定13。
高并发性
垃圾回收的标记、转移、重定位等核心操作均与应用线程并发执行,最大限度减少 STW(Stop-The-World)对应用性能的影响。
无分代设计(JDK 21 前)
早期版本未采用传统分代模型,而是通过 Region 分区动态管理内存,结合染色指针和读屏障技术实现高效并发回收。
硬件架构适配
通过 NUMA 感知优化非统一内存访问架构,优先在本地内存分配对象,提升内存访问效率。
为什么 zgc 毫秒级stw, 而 g1、cms 需要100毫秒级stw?
1 根扫描优化(减少停顿源头)
ZGC通过染色指针直接在指针中存储对象状态(如标记、转移状态) ,GC线程无需遍历内存即可获取元数据,将根扫描时间压缩至1ms以内。
对比传统回收器:
G1/CMS需遍历栈、寄存器等所有GC Roots,堆越大扫描路径越长。例如,64GB堆下G1的初始标记阶段STW可达50ms。
2 全阶段 并发 (消除长STW)
ZGC的标记、转移、重定位全程并发:
并发标记 阶段:染色指针自动记录存活对象,用户线程仅短暂暂停(仅处理 线程栈 扫描)
并发转移 阶段 :通过内存映射表(Forwarding Table)实时更新对象地址,无需暂停线程更新引用
对比起来,G1/CMS 转移阶段 需要STW :
- G1转移阶段必须STW(防止用户线程访问旧对象地址),64GB堆下转移停顿可达200ms
- CMS重新标记阶段需STW修正并发标记的误差,复杂引用场景停顿超100ms
3 零内存碎片设计(避免Full GC)
ZGC通过并发压缩(染色指针指引对象移动)消除内存碎片 ,无需触发Full GC。
对比传统回收器:
- G1/CMS使用标记-清除算法,内存碎片积累后触发Serial Old Full GC(16GB堆下可达2秒)
- CMS需预留20%内存防止并发失败,但突发内存分配仍可能触发STW Full GC
关键差异总结表
| 维度 | ZGC | G1/CMS |
|---|---|---|
| 根扫描耗时 | <1ms(染色指针直接读状态) | 10-100ms(堆越大耗时越长) |
| 转移阶段STW | 0ms(并发转移) | 50-200ms(必须STW更新引用) |
| 内存碎片处理 | 自动并发压缩 | 需Full GC(秒级STW) |
| 最大停顿触发场景 | 始终<1ms(设计目标) | Full GC可达秒级 |
ZGC、CMS、G1 技术演进对比
- ZGC:用空间换时间(染色指针占用部分内存带宽)实现全并发
- G1:区域划分+优先回收降低延迟,但核心阶段仍需STW
- CMS:仅部分阶段并发,内存碎片和浮动垃圾导致最终STW
注:ZGC在JDK11后成熟,适用于大内存低延迟场景;G1/CMS更适用于中小堆且允许适度停顿的业务。
老架构师 尼恩 大白话介绍:ZGC 空间换时间 的思想
ZGC 的核心思想就是“用空间换时间”,它通过一种叫做“染色指针” +“转发表” 的技术,巧妙地利用了内存的一部分空间,来实现更高效的垃圾回收,让程序跑得更顺畅。
- 通过 染色指针(Colored Pointers) 标记对象状态(如是否存活)。
- 结合 读屏障(Load Barrier) + “转发表” , 动态修正引用状态,确保并发标记的准确性。
下面,尼恩用大白话给大家 秒懂一下ZGC。
其实很简单。
可以想象一下,就像快递分拣员用标签来加速分拣包裹一样:
1. 染色指针——给内存地址「贴颜色标签」
想象你是一个快递分拣员,面对 仓库里 无数包裹(内存对象),需要快速判断哪些是垃圾(需回收),哪些是客户还要的(存活对象)。
**传统做法(G1/CMS):**
需要一个一个的挨个拆开包裹,查看里面的单据(遍历对象头部的标记位),耗时费力(STW时间长)。
**ZGC的做法(染色指针):**
给每个包裹的快递单号(内存地址)直接印上颜色标签(例如红=垃圾,绿=存活),这样不用拆包裹,扫一眼单号颜色就能判断状态。
2. 转发表—— 并发转移 用「转发表」避免搬运混乱
想象仓库需要整理包裹,把有用的包裹搬到新货架(内存压缩),同时允许客户继续取件(应用线程运行)。
**传统做法(G1/CMS):**
必须让所有人暂停取件(STW),等搬运工把包裹搬到新地址,再批量更新所有客户的取件地址(引用更新),否则客户会拿到错误包裹。
**ZGC的做法(染色指针+转发表):**
- 搬运时贴新地址:搬运工偷偷把包裹搬到新货架,用一个 映射表(转发表),记录 旧货架贴「旧地址」到新货架地址的映射,同时 染色指针记录转移状态, 根据状态判断要不要走 地址转发。
- 客户自助查地址:客户来取件时,如果发现旧货架有纸条,自动去新地址取件,通过 染色指针+转发表 自动跳转 到 新货架的地址 。

最低0.47元/天 解锁文章
176万+

被折叠的 条评论
为什么被折叠?



