目录
一、ZGC 重要配置参数解析
(一) ZGC 关键参数配置
(二)基本配置前置说明
二、 ZGC 触发时机分析
(一)ZGC 触发机制分类
(二)ZGC 触发机制的基础优化建议
(三)关键日志示例
三、理解和分析 ZGC 日志
(一)ZGC 日志结构解析
(二)关键日志示例
1. 关注核心点标注
2. 日志解析说明
(三)如何利用日志进行调优?
四、理解 ZGC 停顿原因
(一)ZGC 停顿原因分类
(二)详细解析各类停顿
1. GC 过程中 STW 相关停顿
🔹 初始标记(Pause Mark Start)
🔹 再标记(Pause Mark End)
🔹 初始转移(Pause Relocate Start)
2. 非 GC 触发的停顿
🔹 内存分配阻塞(Allocation Stall)
🔹 安全点(Safepoint)
🔹 线程 / 内存 Dump(jstack / jmap)
(三)如何分析 ZGC 停顿日志?
1. 关键排查步骤
2. 整体关注方向
五、总结
干货分享,感谢您的阅读!
在 Java 应用的高并发和低延迟场景下,垃圾回收(GC)机制的优化至关重要。ZGC 作为现代 JVM 提供的一款低延迟垃圾回收器,以其并发回收、高吞吐的特性受到广泛关注。然而,想要真正发挥 ZGC 的优势,合理的参数配置、触发机制优化以及日志分析都是不可或缺的环节。本篇文章深入解析了 ZGC 的关键参数、触发时机、日志分析以及可能的 STW 停顿原因,并提供了具体的优化策略,帮助开发者更高效地利用 ZGC,提升 Java 应用的稳定性和性能。
历史主要基本文章回顾:
涉猎内容 | 具体链接 |
Java GC 基础知识快速回顾 | Java GC 基础知识快速回顾-CSDN博客 |
垃圾回收基本知识内容 | Java回收垃圾的基本过程与常用算法_java垃圾回收过程-CSDN博客 |
CMS调优和案例分析 | CMS垃圾回收器介绍与优化分析案列整理总结_cms 对老年代的回收做了哪些优化设计-CSDN博客 |
G1调优分析 | Java Hotspot G1 GC的理解总结_java g1-CSDN博客 |
ZGC基础和调优案例分析 | 垃圾回收器ZGC应用分析总结-CSDN博客 |
从ES的JVM配置起步思考JVM常见参数优化 | 从ES的JVM配置起步思考JVM常见参数优化_es jvm配置-CSDN博客 |
深入剖析GC问题:如何有效判断与排查 | 深入剖析GC问题:如何有效判断与排查_排查java堆中大对象触发gc-CSDN博客 |
动态扩缩容引发的JVM堆内存震荡调优指南 | 动态扩缩容引发的JVM堆内存震荡:从原理到实践的GC调优指南 |
显式 GC 的使用:留与去,如何选择? | 显式 GC 的使用:留与去,如何选择? |
堆外内存 OOM:现象分析与优化方案 | 堆外内存 OOM:现象分析与优化方案 |
过早晋升的识别与优化实战 | Java垃圾回收的隐性杀手:过早晋升的识别与优化实战 |
如何选取合适的 NewRatio 值 | 如何选取合适的 NewRatio 值来优化 JVM 的垃圾回收策略 |
解决 CMS Old GC 频繁触发 | 解决 CMS Old GC 频繁触发优化 Java 性能的技术方案 |
降低请求高峰期GC Remark阶段停顿时间 | CMS GC优化实战:降低请求高峰期GC Remark阶段停顿时间 |
避免 CMS GC退化操作 | 分析CMS GC退化为单线程串行GC模式的原因与优化 |
解决单次 CMS Old GC 耗时长问题 | 单次 CMS Old GC 耗时长问题分析与优化 |
高效解决MetaSpace OOM 问题 | 深入剖析 MetaSpace OOM 问题:根因分析与高效解决策略 |
减少 Minor GC 频率,优化系统吞吐 | 深入解析 Java GC 调优:减少 Minor GC 频率,优化系统吞吐 |
高频面试题汇总 | JVM高频基本面试问题整理_jvm面试题-CSDN博客 |
一、ZGC 重要配置参数解析
在现代 Java 应用中,ZGC(Z Garbage Collector) 以其低延迟、高吞吐的特性广受欢迎。然而,要充分发挥 ZGC 的优势,合理的参数配置至关重要。
(一) ZGC 关键参数配置
参数 | 作用 | 默认值 | 示例值 | 优化建议 |
---|---|---|---|---|
-Xms / -Xmx | 设置 JVM 堆的最小 / 最大大小 | 物理内存的 1/4 | -Xms10G -Xmx10G | 设为相同值,避免堆扩展影响性能 |
-XX:ReservedCodeCacheSize | JIT 代码缓存区最大大小 | 240MB | -XX:ReservedCodeCacheSize=256m | 适用于 JIT 代码多的应用 |
-XX:InitialCodeCacheSize | JIT 代码缓存区初始大小 | 240MB | -XX:InitialCodeCacheSize=256m | 预留足够空间减少扩容开销 |
-XX:+UnlockExperimentalVMOptions | 解锁实验性 JVM 选项 | 关闭 | -XX:+UnlockExperimentalVMOptions | 必须启用,才能使用 ZGC |
-XX:+UseZGC | 启用 ZGC 作为垃圾回收器 | 关闭 | -XX:+UseZGC | 适用于低延迟场景 |
-XX:ConcGCThreads | 并发 GC 线程数 | CPU 核心数的 12.5% | -XX:ConcGCThreads=2 | 适当增大,提高回收速度 |
-XX:ParallelGCThreads | STW GC 线程数 | CPU 核心数的 60% | -XX:ParallelGCThreads=6 | 适当增加,缩短 STW 时间 |
-XX:ZCollectionInterval | ZGC 最小回收间隔(秒) | 10s | -XX:ZCollectionInterval=120 | 减少频繁回收,降低 GC 开销 |
-XX:ZAllocationSpikeTolerance | 自适应触发 ZGC 的灵敏度 | 2 | -XX:ZAllocationSpikeTolerance=5 | 适用于突发内存分配场景 |
-XX:+UnlockDiagnosticVMOptions | 解锁 JVM 诊断选项 | 关闭 | -XX:+UnlockDiagnosticVMOptions | 便于调试 GC 行为 |
-XX:-ZProactive | 是否启用 ZGC 主动回收 | 开启 | -XX:-ZProactive | 关闭主动回收,减少额外 CPU 开销 |
-Xlog | 配置 GC 日志 | 默认 minimal 级别 | -Xlog:safepoint,gc*=info:file=/opt/logs/gc-%t.log:time,tid,tags:filecount=5,filesize=50m | 便于排查 GC 问题 |
(二)基本配置前置说明
- 固定堆大小(
-Xms
=-Xmx
),避免扩容带来的 GC 开销。 - 调整 GC 线程,适量增加
-XX:ConcGCThreads
和-XX:ParallelGCThreads
以优化吞吐量。 - 合理的 ZGC 触发策略(
-XX:ZCollectionInterval
、-XX:ZAllocationSpikeTolerance
),平衡回收频率和延迟。 - 开启 GC 日志(
-Xlog
),便于监控和优化 GC 行为。
以上配置说明只是基本说明,具体需要在项目上线前进行严格的校对,整体目标:有效提升 ZGC 性能,减少 应用停顿时间,适用于 低延迟高吞吐 的 Java 应用场景。
二、 ZGC 触发时机分析
相比于 CMS 和 G1 的 GC 触发机制,ZGC 采用并发回收,在 GC 过程中仍然允许对象分配。其 核心挑战 在于如何确保 GC 完成前,新分配的对象不会将堆占满,否则会导致 线程停顿,影响应用性能。
(一)ZGC 触发机制分类
触发机制 | 触发条件 | 适用场景 | 关键日志关键字 | 优化参数 |
---|---|---|---|---|
阻塞内存分配请求触发 | 当垃圾未及时回收,导致堆被填满时,部分线程会因无法分配内存而阻塞 | 应避免,否则可能导致应用长时间 STW | Allocation Stall | 需通过 其他触发机制 避免 |
基于分配速率的自适应触发 | 结合 近期分配速率 和 GC 耗时 计算内存阈值,超过则触发 GC | 主要触发方式,适用于 大部分场景 | Allocation Rate | -XX:ZAllocationSpikeTolerance (默认 2,越大越早触发) |
基于固定时间间隔触发 | 每隔一定时间(秒)触发 GC | 适合流量突增场景(如秒杀、定时任务) | Timer | -XX:ZCollectionInterval=120 |
主动触发规则 | ZGC 自行计算 触发时机,时间间隔 非固定 | 适用于默认场景,但可能导致 频繁 GC | Proactive | -XX:-ZProactive 可关闭 |
预热规则 | 服务 刚启动 时触发 | 仅在应用启动阶段出现,一般不需要调整 | Warmup | 无需优化 |
外部触发 | 代码中 显式调用 System.gc() | 人为触发 GC,应 谨慎使用 | System.gc() | 避免手动 System.gc() |
元数据分配触发 | 元数据区不足 时触发 | 较少出现,一般不影响业务 | Metadata GC Threshold | 无需优化 |
(二)ZGC 触发机制的基础优化建议
- 避免 Allocation Stall 触发:一旦日志出现
Allocation Stall
,说明 GC 来不及回收,需 调整 ZGC 触发时机,比如:- 增大
-XX:ConcGCThreads
,提升并发回收能力 - 降低
-XX:ZAllocationSpikeTolerance
,让 ZGC 更早触发 - 减少
-XX:ZCollectionInterval
,提高 GC 触发频率
- 增大
- 针对流量突增场景,建议开启 固定时间间隔触发(
-XX:ZCollectionInterval
),防止 GC 触发过晚,导致瞬间负载过高。 - 主动触发 vs. 固定间隔触发:如果已经开启 固定时间间隔 GC,可以 关闭
-XX:-ZProactive
,避免额外 GC 影响吞吐量。
(三)关键日志示例
可通过 -Xlog:gc*
观察 ZGC 触发情况,示例如下:
[2025-03-08T12:34:56.789+0000][info][gc] GC(42) Pause (Allocation Stall) 2.345ms
[2025-03-08T12:35:01.123+0000][info][gc] GC(43) Concurrent Cycle 456ms
[2025-03-08T12:36:00.567+0000][info][gc] GC(44) Pause (Proactive) 1.678ms
Allocation Stall
:表明 ZGC 触发过晚,需优化触发策略Concurrent Cycle
:表示正常的 ZGC 回收周期Proactive
:主动 GC 触发,若不需要可通过-XX:-ZProactive
关闭
上面只是简单的示例,我们后续会详解这部分。ZGC 触发机制的调优关键在于 提前回收垃圾,避免线程阻塞。理解 各类触发条件 并 结合日志分析,可以有效优化 ZGC,确保应用在高并发场景下的 低延迟、无卡顿运行。
三、理解和分析 ZGC 日志
ZGC 运行时会生成详细的 GC 日志,分析日志可以帮助我们理解 GC 触发原因、回收过程以及堆内存变化。在一次完整的 GC 过程中,日志包含多个阶段,其中部分阶段涉及 Stop-The-World(STW)。
(一)ZGC 日志结构解析
日志项 | 说明 | 是否 STW |
---|---|---|
Start | GC 开始,包含 触发原因(如 Allocation Rate 、Timer 等) | ❌ |
Phase-Pause Mark Start | 初始标记 阶段,标记存活对象 | ✅ |
Phase-Pause Mark End | 再次标记 阶段,确保存活对象不被错误回收 | ✅ |
Phase-Pause Relocate Start | 转移阶段,ZGC 采用 Region 级别的对象移动 | ✅ |
Heap 信息 | 记录 GC 过程中 Mark、Relocate 前后的堆内存使用情况 | ❌ |
GC 信息统计 | 统计 10 秒、10 分钟、10 小时 内的 GC 数据,帮助长期优化 | ❌ |
(二)关键日志示例
1. 关注核心点标注
日志中内容较多,关键点已用红线标出,含义较好理解,详细解释可官网OpenJDK: ZGC查阅资料:
2. 日志解析说明
以下是 GC 日志示例(省略部分内容):
[2025-03-08T12:34:56.123+0000][info][gc] GC(42) Start (Allocation Rate)
[2025-03-08T12:34:56.125+0000][info][gc] GC(42) Phase-Pause Mark Start 2.5ms
[2025-03-08T12:34:56.130+0000][info][gc] GC(42) Phase-Pause Mark End 3.1ms
[2025-03-08T12:34:56.135+0000][info][gc] GC(42) Phase-Pause Relocate Start 1.2ms
[2025-03-08T12:34:56.150+0000][info][gc] GC(42) Heap before GC: High 8.5GB, Low 7.2GB
[2025-03-08T12:34:56.155+0000][info][gc] GC(42) Heap after GC: High 4.2GB, Low 3.1GB
[2025-03-08T12:34:56.200+0000][info][gc] GC(42) GC Info: Last 10s - 4 GCs, Last 10m - 24 GCs
- GC 触发原因:
Start (Allocation Rate)
说明 由于分配速率过高,ZGC 自动触发 GC。 - STW 阶段:
Mark Start
、Mark End
和Relocate Start
都是 STW 阶段,需关注时间是否过长。 - 堆变化:
- GC 前:堆占用 High 8.5GB,Low 7.2GB
- GC 后:堆占用降至 High 4.2GB,Low 3.1GB,说明本次 GC 释放了大量内存。
- 长期 GC 统计:最近 10 秒内触发 4 次 GC,最近 10 分钟触发 24 次 GC,说明 GC 频率较高,可能影响吞吐量。
(三)如何利用日志进行调优?
日志项 | 潜在问题 | 优化建议 |
---|---|---|
Allocation Stall 频繁出现 | GC 触发过晚,导致线程因分配失败阻塞 | 调小 -XX:ZAllocationSpikeTolerance ,加快 GC 触发 |
Phase-Pause Mark Start 耗时较长 | STW 过长,影响延迟 | 增加 -XX:ConcGCThreads ,加快并发标记 |
Heap High Used ≈ 100% | 内存不足,可能影响吞吐 | 增加 -Xmx ,或优化对象分配策略 |
GC Info 统计中 短时间 GC 过于频繁 | GC 过度触发,可能影响吞吐量 | 适当增大 -XX:ZCollectionInterval ,降低 GC 频率 |
ZGC 日志提供了 GC 触发原因、堆使用情况、STW 时间、长期 GC 统计 等关键信息。结合日志分析,可以有效优化 GC 触发时机、吞吐量、内存占用,确保应用在 低延迟、高并发场景 下稳定运行。
四、理解 ZGC 停顿原因
ZGC 以 低延迟(Low Latency) 和 并发回收(Concurrent GC) 为核心设计目标,尽量减少 Stop-The-World(STW) 时间,但仍然存在某些不可避免的停顿场景。根据实战经验,我们总结了 六种导致程序停顿的原因,并结合官方文档进行详细解析。
(一)ZGC 停顿原因分类
停顿类型 | 日志关键字 | STW 时长影响 | 触发原因 |
---|---|---|---|
初始标记(Pause Mark Start) | Phase-Pause Mark Start | 短暂 | GC 启动时,STW 以标记根对象 |
再标记(Pause Mark End) | Phase-Pause Mark End | 短暂 | 标记过程中存活对象的调整 |
初始转移(Pause Relocate Start) | Phase-Pause Relocate Start | 短暂 | 为对象迁移做好准备 |
内存分配阻塞(Allocation Stall) | Allocation Stall | 可能较长 | 堆已满,等待 GC 完成 |
安全点停顿(Safepoint) | VM Operation (safepoint) | 可能较长 | 线程挂起以进入 GC |
线程 / 内存 Dump(Dump Threads / Heap) | jstack / jmap | 取决于 Dump 规模 | 手动执行 jstack、jmap 等工具 |
(二)详细解析各类停顿
1. GC 过程中 STW 相关停顿
ZGC 采用 并发回收,但部分阶段仍需 STW。以下 STW 事件通常较短,不是性能瓶颈,但仍需关注。
🔹 初始标记(Pause Mark Start)
- 日志示例
[2025-03-08T12:34:56.123+0000][info][gc] GC(42) Pause Mark Start 2.5ms
- 作用:
- ZGC 的 GC 采用 染色指针(Colored Pointers) 技术,但仍需 STW 扫描 GC Roots(线程栈、全局变量等)。
- 由于根对象数量通常较少,这个阶段的 STW 时间通常低于 10ms。
- 优化建议:
-XX:ConcGCThreads=N
(调整并发标记线程数)-Xms
与-Xmx
设为相同值,避免动态扩缩容影响 GC
🔹 再标记(Pause Mark End)
- 日志示例
[2025-03-08T12:34:56.130+0000][info][gc] GC(42) Pause Mark End 3.1ms
- 作用:
- 由于 ZGC 并发标记时仍允许对象分配,再标记阶段确保存活对象不会被误回收。
- 这个阶段一般 比初始标记稍长,但通常不会超过 10ms。
- 优化建议:适当增大
-XX:ParallelGCThreads
,减少 STW 时间。
🔹 初始转移(Pause Relocate Start)
- 日志示例
[2025-03-08T12:34:56.135+0000][info][gc] GC(42) Pause Relocate Start 1.2ms
- 作用:
- ZGC 采用 Region 级别的对象迁移(Relocation),此阶段主要做准备工作。
- STW 时长一般 远小于 Mark 阶段,通常 小于 5ms。
2. 非 GC 触发的停顿
🔹 内存分配阻塞(Allocation Stall)
- 日志示例
[2025-03-08T12:34:56.150+0000][warning][gc] Allocation Stall (Heap is Full)
- 触发条件:ZGC 采用 并发回收,但如果分配速率过高,GC 来不及清理内存,就会导致线程因等待 GC 而阻塞。
- 优化建议:
- 减少 Allocation Stall:
- 调整
-XX:ZAllocationSpikeTolerance
,默认值2
,值越大越早触发 GC。 - 增大
-Xmx
,预留足够堆空间。
- 调整
- 监控
Allocation Rate
关键日志:GC(42) Allocation Rate 200MB/s
→ 如果过高,GC 可能来不及回收。
- 减少 Allocation Stall:
🔹 安全点(Safepoint)
- 日志示例
[2025-03-08T12:34:56.200+0000][info][safepoint] VM Operation (safepoint) 10ms
- 作用:
- ZGC 需要 所有线程进入安全点 才能执行 GC,先进入安全点的线程需等待 所有线程 进入。
- 线程阻塞的时间取决于 最慢的线程。
- 优化建议:
- 避免过多 长时间运行的 Native 方法(会阻塞安全点)。
- 使用
-XX:+UseLargePages
以减少安全点同步的开销。
🔹 线程 / 内存 Dump(jstack / jmap)
- 日志示例
[2025-03-08T12:34:56.300+0000][info][heap] Heap Dump initiated by jmap
- 触发条件:执行
jstack
、jmap
或jcmd
可能会引发 STW 停顿,取决于 Dump 规模。 - 优化建议:生产环境避免频繁 Dump,可使用 Async-profiler 或
perf
进行低开销分析。这里如果有公司级的手段就直接使用,以笔者工作过的美团和支付宝为例,内部的工具整体上已经够用了。
(三)如何分析 ZGC 停顿日志?
1. 关键排查步骤
- 检查
Allocation Stall
是否出现:如频繁发生,调整-XX:ZAllocationSpikeTolerance
。 - 关注
Pause Mark Start
/Pause Mark End
耗时:STW 超过 10ms,考虑增大-XX:ConcGCThreads
。 - 监控
Safepoint
耗时:若时间过长,检查是否有 长时间运行的 Native 方法。 - 分析
Heap Dump
触发原因:避免频繁jmap
,可用-XX:+HeapDumpOnOutOfMemoryError
在 OOM 时自动 Dump。
2. 整体关注方向
- ZGC 的 STW 停顿通常较短,但 内存分配阻塞 可能会导致较长停顿,应重点优化
ZAllocationSpikeTolerance
和Xmx
配置。 - 安全点(Safepoint) 可能会影响 STW,避免 长时间运行的 Native 方法。
- Heap Dump 和 jstack 操作 可能引发 STW,应 减少在线环境频繁使用。
- 定期分析 GC 日志,监控
Allocation Rate
、Heap Used
,避免分配速率过高导致 GC 触发延迟。
ZGC 虽然减少了 STW,但并非完全无停顿,合理的参数优化仍然是提升性能的关键! 🚀
五、总结
ZGC 作为一款低延迟 GC,适用于高吞吐、低停顿的 Java 应用场景。通过合理配置参数、优化触发机制以及深入分析 GC 日志,可以有效降低 GC 对应用性能的影响。本文详细解析了 ZGC 的核心参数、触发原理、关键日志以及常见的停顿问题,并提供了针对性的优化建议。希望这些内容能够帮助开发者更好地理解和调优 ZGC,使 Java 应用在复杂业务环境下依然能保持高效、稳定运行。