← 返回题目列表

如何分析 GC 日志?看哪些关键指标判断 GC 是否健康?

困难 第 23 / 34 题 更新于 2026/07/28
GC日志GC调优停顿时间吞吐量

简化版

开启 GC 日志:JDK 8 用 -XX:+PrintGCDetails -Xloggc:gc.log;JDK 9+ 用统一日志 -Xlog:gc*:file=gc.log:time,uptime看哪些关键指标判断健康① GC 停顿时间(Pause)——单次 STW 多久,尤其 Full GC 的停顿(几百毫秒到几秒都可能,是延迟杀手);② GC 频率——多久一次 Young GC、多久一次 Full GC(Full GC 应该很少,频繁 Full GC 是大问题);③ 吞吐量——GC 时间占总时间的比例(健康的应用 GC 占比应 <5%);④ 回收效果——每次 GC 后堆用了多少、回收了多少(如果 Full GC 后老年代还是很满,说明有内存泄漏或堆太小);⑤ 晋升情况——对象是否过早/过多晋升到老年代。排查思路:频繁 Young GC → 年轻代太小;频繁 Full GC → 老年代太小 / 内存泄漏 / 大对象 / 元空间不足;GC 后老年代降不下来 → 内存泄漏。工具:GCEasy、GCViewer 可视化分析 GC 日志。

详细版

GC 日志一行怎么读(以 JDK 8 Parallel GC 为例):

2026-01-01T10:00:00.000+0800: 5.234: [GC (Allocation Failure)
  [PSYoungGen: 65536K->8192K(76288K)] 120000K->75000K(250880K), 0.0234567 secs]

拆解:
  5.234                    → JVM 启动后 5.234 秒发生
  GC (Allocation Failure)  → 触发原因:年轻代分配失败(正常的 Young GC)
  PSYoungGen: 65536K->8192K(76288K)  → 年轻代:GC 前 64M → GC 后 8M(容量 74.5M)
  120000K->75000K(250880K) → 整个堆:GC 前 117M → GC 后 73M(总容量 245M)
  0.0234567 secs           → 本次 GC 停顿 23 毫秒

关键指标与健康标准

指标怎么看健康标准
Young GC 停顿每次 [GC] 的 secs一般几毫秒~几十毫秒
Full GC 停顿[Full GC] 的 secs越少越好,单次可能几百 ms~几秒
Full GC 频率多久一次 Full GC应该很少(几小时/几天一次或没有)
GC 吞吐量GC 总时间 / 运行总时间GC 占比 <5% 较健康
老年代回收效果Full GC 后老年代占用应明显下降;降不下来 = 泄漏

⚠️ 最危险的信号是「频繁 Full GC 且每次回收后老年代占用仍然很高」——这几乎肯定是内存泄漏(对象一直被引用,GC 回收不掉,老年代越来越满,于是不断触发 Full GC 想腾空间但腾不出来)。这时应该 dump 堆(jmap -dump-XX:+HeapDumpOnOutOfMemoryError)用 MAT 分析哪些对象占满了老年代。另一个危险信号是「Full GC 频繁但堆并不满」——可能是元空间不足、或代码里显式调 System.gc()

完整版教学

一、怎么开启和获取 GC 日志

分析 GC 的第一步是开启日志——JDK 8 和 9+ 参数不同:

JDK 8(及以前):
  -XX:+PrintGCDetails           打印 GC 详情
  -XX:+PrintGCDateStamps        打印日期时间戳
  -Xloggc:/path/gc.log          输出到文件
  -XX:+UseGCLogFileRotation     日志滚动(防止单文件过大)
  -XX:NumberOfGCLogFiles=5
  -XX:GCLogFileSize=20M

JDK 9+(统一日志框架 -Xlog):
  -Xlog:gc*:file=gc.log:time,uptime,level,tags:filecount=5,filesize=20M
  (JDK 8 的 PrintGCDetails 等在 JDK 9+ 被 -Xlog 统一替代)

开启 GC 日志几乎是「零成本」的(开销极小),生产环境应该常态开启并滚动保存——出问题时 GC 日志是排查内存问题的第一手资料。注意 JDK 9 引入了统一日志框架 -Xlog,旧参数被废弃。理解「JDK8 用 PrintGCDetails+Xloggc、JDK9+ 用 -Xlog、生产应常态开启并滚动」,就迈出了 GC 分析的第一步——先有日志才能分析。

二、读懂一行 GC 日志

看懂 GC 日志的每个字段是分析的基础:

[GC (Allocation Failure) [PSYoungGen: 65536K->8192K(76288K)]
   120000K->75000K(250880K), 0.0234567 secs]

字段            含义
──────────────────────────────────────────────
GC / Full GC    GC 类型(Young GC 还是 Full GC)
Allocation Failure  触发原因(年轻代满了分配失败)
PSYoungGen      年轻代(Parallel Scavenge 的年轻代)
65536K->8192K   该区域 GC 前 -> GC 后 占用
(76288K)        该区域的总容量
120000K->75000K 整个堆 GC 前 -> GC 后
0.0234567 secs  停顿时长

读懂一行的核心是三组数字:**① 区域占用变化(GC前→GC后)**看回收了多少;② 括号里的容量看区域多大;③ secs 看停顿多久。比如 65536K->8192K 说明年轻代回收了 56M(大部分是垃圾,正常);如果是老年代 200000K->198000K 只回收了 2M 却还很满,就是危险信号。理解「GC 日志一行的关键是区域占用前后变化、容量、停顿时长这三组数字」,就能读懂每次 GC 发生了什么。

三、关键指标一:停顿时间

**GC 停顿时间(STW Pause)**是延迟敏感应用最关心的指标:

停顿时间看什么:
  ① Young GC 停顿:通常几毫秒~几十毫秒(可接受)
     - 如果 Young GC 停顿很长 → 年轻代太大 或 存活对象太多
  ② Full GC 停顿:几百毫秒~几秒(延迟杀手!)
     - Full GC 要扫描整个堆,堆越大停顿越长
     - 对延迟敏感的应用,一次几秒的 Full GC = 用户明显卡顿

算例:一个堆 8G 的应用
  - Young GC 停顿 20ms,每 10 秒一次 → 影响很小
  - Full GC 停顿 2 秒,每小时一次 → 每小时卡 2 秒,可能不可接受
  → 所以低延迟应用要用 G1/ZGC,把停顿控制在几毫秒~几十毫秒

停顿时间直接决定「用户感受到的卡顿」——Young GC 停顿一般短(几十毫秒内可接受),Full GC 停顿是重点(几百毫秒到几秒,是延迟杀手)。对低延迟应用,一次几秒的 Full GC 就是灾难,所以要选低停顿的收集器(G1 目标可控停顿、ZGC 停顿 <10ms)。理解「停顿时间尤其 Full GC 停顿是延迟杀手、堆越大 Full GC 越慢、低延迟应用用 G1/ZGC 控制停顿」,就理解了延迟视角的 GC 分析。

四、关键指标二:GC 频率与吞吐量

GC 频率吞吐量反映 GC 对整体性能的拖累:

GC 频率:
  - Young GC 频繁(如每秒好几次)→ 年轻代太小,对象快速填满
    → 调大年轻代(-Xmn)减少频率
  - Full GC 频繁(如每分钟好几次)→ 严重问题!
    → 老年代太小 / 内存泄漏 / 大对象直接进老年代 / 元空间不足

吞吐量(Throughput):
  吞吐量 = 应用运行时间 / (应用运行时间 + GC时间)
  = 1 - GC占比
  例:1 小时里 GC 累计花了 3 分钟 → GC 占比 5%,吞吐量 95%
  健康标准:GC 占比 <5%(吞吐优先应用可放宽,但 >10% 就要优化)

频率:Young GC 频繁说明年轻代太小(对象快填满),Full GC 频繁是严重问题(老年代太小/泄漏/大对象/元空间不足)。吞吐量:GC 时间占总时间的比例,健康应 <5%——如果 GC 占了 20% 的时间,说明程序有 1/5 的时间在做 GC 而不是干活,严重拖累性能。理解「Young GC 频繁=年轻代太小、Full GC 频繁=严重问题、吞吐量=1-GC占比应<5%」,就理解了频率和吞吐视角的分析。

五、关键指标三:回收效果与晋升

回收效果晋升情况能揭示更深层的问题(如内存泄漏):

回收效果(尤其看老年代):
  正常:Full GC 后老年代占用明显下降(如 200M->50M,回收了 150M)
  异常:Full GC 后老年代还是很满(如 200M->198M,几乎没回收)
    → 老年代对象都还被引用着,回收不掉 → 内存泄漏!
    → 老年代会越来越满,Full GC 越来越频繁,最终 OOM

晋升情况(对象从年轻代到老年代):
  正常:大部分对象在 Young GC 就死了,少量长寿对象才晋升
  异常:大量对象很快晋升到老年代
    → 可能年轻代太小(对象没来得及在年轻代死就被晋升)
    → 或有很多中等寿命对象 → 老年代压力大 → Full GC 频繁

回收效果是判断内存泄漏的关键——Full GC 后老年代降不下来 = 内存泄漏(对象都被引用着回收不掉)。晋升反映对象生命周期——大量对象过早晋升说明年轻代太小或有很多中等寿命对象,会加重老年代和 Full GC 压力。这两个指标比单纯的停顿/频率更能揭示「根因」。理解「Full GC 后老年代降不下来=内存泄漏、大量过早晋升=年轻代小或中寿命对象多」,就能从 GC 日志诊断深层问题。

六、常见问题的排查思路

把典型问题和排查思路串起来,形成诊断地图:

现象                        → 可能原因                → 排查/解决
──────────────────────────────────────────────────────────────
Young GC 频繁               年轻代太小                调大 -Xmn / -Xnn
Young GC 停顿长             年轻代太大/存活对象多      调小年轻代 或 换 G1
Full GC 频繁+回收不掉       内存泄漏                  dump 堆用 MAT 找泄漏
Full GC 频繁+堆没满         元空间不足/System.gc()    调大元空间/禁显式GC
Full GC 停顿几秒            堆大+吞吐收集器           换 G1/ZGC 降停顿
对象过早晋升               年轻代小/Survivor 小      调大年轻代/调 TargetSurvivorRatio
一直 Full GC 然后 OOM      内存泄漏 或 堆太小         dump 分析 或 调大堆

排查的核心逻辑:先看是 Young GC 还是 Full GC 的问题 → 再看频率、停顿、回收效果 → 定位到年轻代/老年代/元空间 → 判断是「参数不合理」还是「内存泄漏」。最需要警惕的是「频繁 Full GC 且回收不掉」——这基本是内存泄漏,要 dump 堆(-XX:+HeapDumpOnOutOfMemoryErrorjmap)用 MAT 分析。工具上,GCEasy、GCViewer 能把 GC 日志可视化(停顿分布、吞吐量、内存趋势),比肉眼看日志高效得多。理解这张「现象→原因→解决」的诊断地图,就掌握了 GC 日志分析的实战方法。

记忆钩子:「GC 日志分析五指标:① 停顿时间(尤其 Full GC 停顿=延迟杀手)② 频率(Young 频繁=年轻代小、Full 频繁=大问题)③ 吞吐量(GC 占比应<5%)④ 回收效果(Full GC 后老年代降不下来=内存泄漏!)⑤ 晋升(过早晋升=年轻代小);最危险信号=频繁 Full GC + 回收不掉 → dump 堆用 MAT 查泄漏;开启:JDK8 -XX:+PrintGCDetails -Xloggc,JDK9+ -Xlog:gc*;可视化用 GCEasy/GCViewer」

七、常见误区与追问

  • 误区:GC 越少越好、最好没有 GC。 Young GC 是正常且必要的(对象生生死死很正常),频繁 Young GC 才需关注;真正要警惕的是频繁 Full GC。追求「没有 GC」不现实也没必要。
  • 误区:Full GC 后堆没降多少是正常的。 恰恰是危险信号——Full GC 后老年代应明显下降,如果降不下来(如 200M->198M)说明对象都被引用着回收不掉,几乎肯定是内存泄漏,会越来越频繁 Full GC 最终 OOM。
  • 误区:GC 停顿长只能靠调大堆解决。 调大堆反而可能让 Full GC 停顿更长(要扫描的堆更大);低延迟场景应换低停顿收集器(G1 控制停顿目标、ZGC 停顿<10ms),而非一味调大堆。
  • 误区:吞吐量和停顿时间可以同时最优。 二者往往是权衡——吞吐优先(Parallel GC)停顿可能长;低停顿(G1/ZGC)会牺牲一点吞吐(并发 GC 占用 CPU);要根据应用是「延迟敏感」还是「吞吐敏感」来选。
  • 追问:怎么区分内存泄漏和堆设置太小? 看 Full GC 后老年代能否降下来——能降下来但很快又满=堆太小(调大堆);降不下来(一直很满)=内存泄漏(dump 堆用 MAT 找「谁引用了大量对象」)。泄漏时堆用量随时间单调上升。
  • 追问:频繁 Full GC 但堆并不满,可能是什么? ① 元空间(Metaspace)不足触发 Full GC(调大 -XX:MaxMetaspaceSize);② 代码里显式调 System.gc()(加 -XX:+DisableExplicitGC 禁用);③ 老年代碎片化(CMS 场景,换 G1)。
  • 追问:分析 GC 日志有哪些工具? GCEasy(在线,上传日志出可视化报告)、GCViewer(开源桌面工具,画停顿/吞吐/内存趋势图);配合堆分析用 MAT(Eclipse Memory Analyzer)、jvisualvm、Arthas。

八、加强记忆

开启 GC 日志:JDK 8 用 -XX:+PrintGCDetails -Xloggc:gc.log(生产常态开启并滚动),JDK 9+ 用统一日志 -Xlog:gc*读一行 GC 日志看三组数字:区域占用(GC前→GC后,看回收多少)、括号容量、secs(停顿)。五大健康指标① 停顿时间——尤其 Full GC 停顿(几百 ms~几秒,延迟杀手),低延迟应用用 G1/ZGC;② 频率——Young GC 频繁=年轻代太小,Full GC 频繁=严重问题(老年代小/泄漏/大对象/元空间不足);③ 吞吐量——GC 时间占比应 <5%④ 回收效果——Full GC 后老年代降不下来=内存泄漏!(对象被引用着回收不掉);⑤ 晋升——大量过早晋升=年轻代太小或中寿命对象多。最危险信号:频繁 Full GC + 回收不掉 → dump 堆-XX:+HeapDumpOnOutOfMemoryError / jmap)用 MAT 找泄漏。区分泄漏 vs 堆太小:Full GC 后老年代能否降下来。可视化工具:GCEasy、GCViewer。一句话「GC 日志看五指标:停顿(Full GC 是延迟杀手)/频率(Full 频繁=大问题)/吞吐(<5%)/回收效果(老年代降不下来=泄漏)/晋升(过早=年轻代小),最危险是频繁 Full GC + 回收不掉→dump 堆用 MAT」。