Skip to content

堆空间 GC 举例与日志分析

GC 日志:JVM 的黑匣子

GC 日志记录了每一次垃圾回收的详细信息。学会读 GC 日志,是排查 GC 问题和性能调优的基础。

开启 GC 日志

bash
# JDK 8 及之前:常用参数
java -Xms256m -Xmx256m \
     -XX:+PrintGCDetails \          # 打印详细 GC 信息
     -XX:+PrintGCDateStamps \        # 打印时间戳
     -XX:+PrintTenuringDistribution \ # 打印晋升年龄分布
     -Xloggc:/tmp/gc.log \           # 输出到文件
     MyApp

# JDK 9+:统一日志框架
java -Xms256m -Xmx256m \
     -Xlog:gc*:file=/tmp/gc.log:time,uptime,level,tags \
     MyApp

MinorGC 日志解析

bash
# JDK 8 风格的 MinorGC 日志:
2026-03-22T10:15:30.123+0800: [GC (Allocation Failure) 
  Desired survivor size 5242880 bytes, new threshold 15 (max 15)
  - age   1:   1048576 bytes,   1048576 total
  eden space 2621440K, 2595328K used (99%)
  from space 524288K, 0K used (0%)
  to   space 524288K, 0K used (0%)
  : 2595328K->1048576K(3145728K), 0.0234567 secs] 
  [Times: user=0.10 sys=0.01, real=0.02 secs]

关键字段解读

eden space 2621440K, 2595328K used (99%)
↑ Eden 区总大小 2.5GB,使用了 99%

from space 524288K, 0K used (0%)
to   space 524288K, 0K used (0%)
↑ 两个 Survivor 区,S0 和 S1
↑ MinorGC 后,对象从 Eden 和 From Survivor 复制到 To Survivor

2595328K->1048576K(3145728K)
↑ GC 前占用 2595MB,GC 后占用 1048MB
↑ 括号内 3145728K = 年轻代总大小

0.0234567 secs
↑ 停顿时间 23.5 毫秒

[Times: user=0.10 sys=0.01, real=0.02]
↑ user=0.10s: GC 线程消耗的 CPU 时间(所有 GC 线程累加)
↑ sys=0.01s: OS 调用消耗的时间
↑ real=0.02s: 实际墙钟时间

FullGC 日志解析

bash
# FullGC 日志:
2026-03-22T10:15:45.678+0800: [Full GC (Allocation Failure)
  [CMS: 1572864K->1468006K(1572864K), 1.2345678 secs]
  1572864K->1468006K(2097152K), 2.3456789 secs]
[Times: user=3.45 sys=0.12, real=2.35 secs]

关键字段解读

Full GC (Allocation Failure)
↑ 触发原因:分配失败(老年代空间不足)

[CMS: 1572864K->1468006K(1572864K), 1.23 secs]
↑ CMS 收集器的收集结果
↑ 老年代从 1572864K(1.5GB) 降到 1468006K(1.4GB)
↑ 括号内是老年代的容量
↑ 停顿了 1.23 秒

1572864K->1468006K(2097152K)
↑ 整个堆从 1572864K 降到 1468006K
↑ 括号内是堆的总容量

2.3456789 secs
↑ FullGC 总停顿时间 2.35 秒(较长!)

user=3.45 sys=0.12, real=2.35
↑ 多线程 GC,user 时间 > real 时间

G1 GC 日志解析

G1 的日志与其他 GC 收集器有较大不同:

bash
# G1 的 MinorGC
2026-03-22T10:16:00.123: [GC pause (G1 Evacuation Pause) (young)
  Using 8 workers
  , 0.0123456 secs]
   [Eden: 1536.0M(1536.0M)->0.0B 
    Survivors: 96.0M->128.0M 
    Heap: 4096.0M(4096.0M)->2048.0M(4096.0M)]

# G1 的 MixedGC
2026-03-22T10:16:30.456: [GC pause (G1 Evacuation Pause) (mixed)
   [Eden: 1024.0M(1024.0M)->0.0B 
    Survivors: 64.0M->32.0M 
    Heap: 4096.0M(4096.0M)->2560.0M(4096.0M)]
   [Choose CSet: 512.0M]

G1 日志特点

G1 把堆划分为多个 Region,日志中可以看到 Eden/Survivor/Humongous 类型的 Region。

  • young:纯年轻代收集
  • mixed:混合收集,同时回收年轻代和部分老年代
  • initial mark:并发标记的开始
  • remark:并发标记的结束

实战案例:识别 GC 问题

案例一:频繁 FullGC,内存不断增长

[Full GC (Allocation Failure) 
  1000K -> 999K (1000K), 0.100 secs]
[Full GC (Allocation Failure) 
  1010K -> 1009K (1000K), 0.100 secs]
[Full GC (Allocation Failure) 
  1020K -> 1019K (1000K), 0.100 secs]

诊断:每次 FullGC 后内存几乎没降,说明有大量对象无法回收——典型的内存泄漏大对象问题。

案例二:MinorGC 后大量对象晋升

[GC (Allocation Failure) 
  eden 500M->50M,  S0 50M->50M(50M),  age 1:  80M,  S0 满了,  age >= 15:  200M]

诊断:Survivor 区满了(S0->S0(50M)),大量年龄>=1的对象(80M)无法进入 Survivor,被强制晋升老年代。

解决方案:增大 Survivor 区(-XX:SurvivorRatio)或降低晋升阈值。

案例三:GC 时间过长

[Full GC: 4000M->3500M(4000M), 5.678 secs]

诊断:FullGC 停顿 5.7 秒,这对在线服务是致命的。

解决方案

  • 减少堆大小(减小 GC 扫描范围)
  • 升级 GC 收集器(G1、ZGC)
  • 减少大对象直接进入老年代

GC 日志分析工具

工具特点适用场景
GCEasy在线工具,上传 gc.log,自动分析快速定位问题
GCViewer开源桌面工具本地离线分析
IBM PMATIBM GC 专用分析 IBM J9 GC 日志
jstatJDK 内置命令行工具实时监控
Arthas阿里开源,命令行线上诊断
bash
# jstat:实时查看 GC 统计
jstat -gcutil <pid> 1000
# 输出:
# S0 S1 E O M CCS YGC YGCT FGC FGCT CGC CGCT GCT
# 0.00 52.00 65.00 78.00 92.00 88.00 123 2.34 5 12.34 0 0.00 14.68

本节小结

GC 日志分析的核心要点:

指标含义
Eden/S0/S1/O各区域的内存使用率
GC TimeGC 停顿时间
User/Sys/RealCPU时间/OS时间/墙钟时间
Allocation Failure触发原因:分配失败

通过 GC 日志,你可以发现:

  • GC 频率:是否过于频繁
  • GC 时间:是否停顿过长
  • 内存趋势:是否持续增长
  • 晋升情况:是否有异常晋升

下一节,我们来看方法区(元空间)的核心内容——方法区概述(与栈/堆交互)

基于 VitePress 构建