如何查看和分析 GC 日志?在调优时你主要关注 GC 日志中的哪些指标?
查看和分析 GC(垃圾收集)日志是 Java 性能调优和排查内存溢出(OOM)问题的核心技能。以下是关于如何输出、分析 GC 日志,以及调优时重点关注指标的详细指南。
第一部分:如何查看和分析 GC 日志
1. 开启 GC 日志输出
要想查看 GC 日志,首先需要在启动 Java 应用时配置相应的 JVM 参数。注意 JDK 8 和 JDK 9+ 的参数完全不同。
JDK 8 及之前版本:
bash
-XX:+PrintGCDetails # 打印 GC 详细信息
-XX:+PrintGCDateStamps # 打印 GC 发生的时间戳(绝对时间)
-XX:+PrintGCTimeStamps # 打印 GC 发生的时间(相对于 JVM 启动的时间)
-XX:+PrintHeapAtGC # 在 GC 前后打印堆的详细信息(可选)
-Xloggc:/path/to/gc.log # 指定 GC 日志文件的存储路径
-XX:+UseGCLogFileRotation # 开启日志滚动
-XX:NumberOfGCLogFiles=5 # 保留的日志文件个数
-XX:GCLogFileSize=20M # 每个日志文件的大小
JDK 9 及之后版本(引入了统一日志框架 Unified Logging):
bash
-Xlog:gc*:/path/to/gc.log:time,tags,level:filecount=5,filesize=20M
(注:gc* 表示打印所有与 GC 相关的标签日志,time 表示时间戳,滚动配置也集成在了这一条参数中。)
2. 手动阅读 GC 日志(以 JDK 8 的 Parallel GC 为例)
一段典型的 Minor GC(Young GC)日志如下:
plaintext
2023-10-25T10:00:00.123+0800: 2.531: [GC (Allocation Failure) [PSYoungGen: 33280K->5118K(38400K)] 33280K->5126K(125952K), 0.0043683 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
日志拆解分析:
2023-10-25T.../2.531: GC 发生的绝对时间与相对 JVM 启动的时间。GC (Allocation Failure): 触发原因。这里是因为年轻代空间不足(分配失败)触发了 Minor GC。如果是Full GC (System.gc())则是代码显式调用触发。PSYoungGen: 33280K->5118K(38400K): 年轻代情况。GC 前使用了 33M,GC 后剩下 5M,年轻代总大小 38M。33280K->5126K(125952K): 整个堆情况。GC 前堆使用 33M,GC 后堆使用 5M,堆总大小 125M。0.0043683 secs: 本次 GC 消耗的时间。[Times: user=0.01 sys=0.00, real=0.01 secs]:user: CPU 用户态消耗时间(多核累加)。sys: CPU 内核态消耗时间。real: 实际墙钟时间(Wall Clock Time),即应用停顿(STW)的真实时间,这是我们最需要关注的。
3. 借助工具自动化分析 GC 日志
人工阅读 GC 日志极其低效,通常使用专业工具进行可视化分析:
- GCEasy (推荐,在线工具):上传 gc.log,一键生成极为详尽的图表(包括吞吐量、最大停顿时间、内存泄漏警告等)。官网:gceasy.io
- GCViewer (离线桌面工具):开源的 Java 应用程序,导入日志后可生成直观的图表。
- APM 监控系统:如 Prometheus + Grafana、SkyWalking、DataDog 等,可以实时监控 GC 频率和耗时(不需要解析日志,直接通过 JMX/Agent 抓取)。
- Arthas:阿里开源的诊断工具,使用
dashboard或jvm命令可以实时看 GC 统计。
第二部分:调优时主要关注的 GC 指标
在进行 JVM 调优时,核心是寻找吞吐量(Throughput)、延迟/停顿时间(Latency/Pause Time) 和 内存占用(Footprint) 之间的平衡。我主要会盯住以下几个关键指标:
1. 停顿时间(Pause Time / STW Time)
这是大多数面向用户的系统(如 Web 服务、API 网关)最关注的指标。
- 最大停顿时间(Max Pause Time):单次 Full GC 带来的最长 STW 是多少?如果超过 1 秒或几秒,可能会导致接口超时、数据库连接断开等严重问题。
- 平均停顿时间(Average Pause Time):Young GC 的平均耗时。通常希望控制在 10ms ~ 50ms 以内。
2. 吞吐量(Throughput)
吞吐量 = 运行用户代码时间 / (运行用户代码时间 + GC 停顿时间)
- 指标要求:高并发后台处理系统(如批处理、数据清洗)更看重此指标。通常要求吞吐量在 95% 以上,优秀的系统能达到 99% 以上。
- 如果吞吐量低于 90%,说明 CPU 把大量时间花在了垃圾回收上,系统处于亚健康或即将 OOM 的状态。
3. GC 频率(GC Frequency)
- Minor GC (Young GC) 频率:多久发生一次?如果每秒发生好几次,说明年轻代太小或短生命周期对象产生过快。合理的频率通常是几秒到几十秒一次。
- Full GC / Major GC 频率:这是重中之重!
- 正常的应用:一天只发生几次,甚至几天发生一次 Full GC,且都在业务低谷期。
- 异常的应用:每小时甚至每分钟都在 Full GC。必须立即排查内存泄漏或调整老年代大小。
4. 内存晋升率(Promotion Rate)与老年代占用
- 晋升率:每次 Young GC 后,有多少数据存活并被移入了老年代?如果晋升过快(Premature Promotion,对象过早晋升),会导致老年代迅速填满,引发频繁 Full GC。
- GC 后的堆内存占用量:
- 健康状态:每次 Full GC 后,堆内存使用量能回落到一个较低且稳定的基线。
- 内存泄漏预兆:观察 GC 图表,如果每次 Full GC 后,老年代的内存使用基线不断抬高,形成一个斜向上的阶梯,最终一定会 OOM。
5. GC 触发原因(GC Cause)
关注日志中括号里的触发原因,可以快速定位问题:
Allocation Failure:最常见,空间不足,通常是正常的。System.gc():代码中被手动调用了System.gc()。强烈建议通过启动参数-XX:+DisableExplicitGC屏蔽掉。Metadata GC Threshold:元空间(Metaspace)满了触发的 Full GC。说明需要调大-XX:MaxMetaspaceSize或排查动态类加载(如反射、CGLib)引起的类泄漏。Promotion Failure/Concurrent Mode Failure:(CMS 和 G1 特有)年轻代对象往老年代转移时,老年代空间不够,或者内存碎片化严重。通常需要增大老年代,或者提早触发并发垃圾回收(降低触发阈值)。
总结:调优实战口诀
当你拿到一份 GC 日志(或 GCEasy 的报告)时,排查顺序通常是:
- 看 Full GC 频率:频繁?-> 去查内存泄漏(Dump 堆快照用 MAT 分析),或者加大堆内存。
- 看 Full GC 停顿时间:太长?-> 考虑更换垃圾收集器(如从 Parallel 换到 G1 或 ZGC),或者减小堆大小(配合分布式扩容)。
- 看 Young GC 频率:太快?-> 增大年轻代(
-Xmn)。 - 看老年代曲线:一直涨不降?-> 100% 内存泄漏。
右滑查看面试常问