基于本文回答
0
评论

如何查看和分析 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:阿里开源的诊断工具,使用 dashboardjvm 命令可以实时看 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 的报告)时,排查顺序通常是:

  1. 看 Full GC 频率:频繁?-> 去查内存泄漏(Dump 堆快照用 MAT 分析),或者加大堆内存。
  2. 看 Full GC 停顿时间:太长?-> 考虑更换垃圾收集器(如从 Parallel 换到 G1 或 ZGC),或者减小堆大小(配合分布式扩容)。
  3. 看 Young GC 频率:太快?-> 增大年轻代(-Xmn)。
  4. 看老年代曲线:一直涨不降?-> 100% 内存泄漏。
右滑查看面试常问