跳到主要内容

频繁 Full GC 排查

问题

线上服务频繁触发 Full GC,接口 RT 飙升甚至超时,如何排查和解决?

答案

Full GC 的影响

  • STW(Stop The World):Full GC 时所有业务线程暂停
  • G1 一次 Full GC 可能几秒到几十秒
  • 频繁 Full GC → 接口频繁超时 → 服务不可用

排查流程

第一步:确认 GC 频率和原因

jstat 查看 GC 频率
# 每秒打印一次 GC 统计
jstat -gcutil <pid> 1000

# S0 S1 E O M CCS YGC YGCT FGC FGCT
# 0.00 45.23 78.12 95.67 92.34 88.90 234 3.456 45 98.765
# ↑ 老年代使用率 95% ↑ Full GC 45 次

# 每分钟多次 Full GC 就是频繁了

第二步:分析 GC 日志

JVM GC 日志参数(JDK 11+)
-Xlog:gc*:file=gc.log:time,level,tags:filecount=5,filesize=100m
GC 日志关键信息
[2024-01-15T10:23:45] GC(1234) Pause Full (Allocation Failure)
[2024-01-15T10:23:45] GC(1234) Old: 3800M -> 3750M (4096M) ← 回收了才 50M!
[2024-01-15T10:23:48] GC(1234) Pause Full 3.2s ← STW 3.2 秒
关键判断

Full GC 后老年代从 3800M 只降到 3750M = 几乎没回收 → 大概率内存泄漏。

常见原因与解决

原因 1:大对象直接进入老年代

问题:大数组/大集合直接分配到老年代
// 每次请求创建 5MB 的临时数据
byte[] buffer = new byte[5 * 1024 * 1024]; // 超过阈值直接进老年代

解决:

  • 减小 -XX:PretenureSizeThreshold(仅 Serial / ParNew 有效)
  • 增大 Young 区:-Xmn 或调整 -XX:NewRatio
  • 优化代码避免大对象

原因 2:Young 区太小导致过早晋升

调整新生代大小
# 增大新生代,让对象在 Young 区就被回收
-Xmn2g

# 或调整比例(默认 NewRatio=2,即 Old:Young = 2:1)
-XX:NewRatio=1 # Old:Young = 1:1

原因 3:内存泄漏

老年代持续增长,Full GC 回收效果差。参见 内存泄漏排查

原因 4:Metaspace 触发 Full GC

# 增大元空间(默认无上限,但某些数据框架会设置)
-XX:MaxMetaspaceSize=512m

# 监控元空间
jstat -gcmetacapacity <pid>

原因 5:System.gc() 显式调用

// 某些框架(如 NIO / RMI)会调用 System.gc()
System.gc(); // 触发 Full GC

// 可以禁用
-XX:+DisableExplicitGC

GC 调优常用参数

G1 GC 调优
# 堆大小
-Xms4g -Xmx4g # 初始=最大,避免动态扩缩

# G1 停顿时间目标
-XX:MaxGCPauseMillis=200 # 目标停顿 200ms

# 老年代触发混合回收阈值
-XX:InitiatingHeapOccupancyPercent=45 # 老年代占 45% 时触发 Mixed GC

# Region 大小
-XX:G1HeapRegionSize=8m

Arthas 在线排查

Arthas 快速排查
# 查看 JVM 内存概况
dashboard

# 查看 GC 信息
jvm | grep gc

# 内存对象统计
memory

常见面试问题

Q1: Full GC 和 Young GC 的区别?

答案

Young GC (Minor GC)Full GC (Major GC)
范围新生代整个堆 + 元空间
频率高(秒级)低(应尽量避免)
耗时短(毫秒级)长(秒级)
触发Eden 满老年代满 / 元空间满 / System.gc()

Q2: 什么情况下对象会直接进入老年代?

答案

  1. 大对象(超过 -XX:PretenureSizeThreshold
  2. 年龄达到阈值(默认 15,-XX:MaxTenuringThreshold
  3. Survivor 区装不下的对象
  4. 动态年龄判断:某个年龄以上的对象总大小超过 Survivor 一半

详见 垃圾回收算法

Q3: CMS 和 G1 Full GC 有什么区别?

答案

  • CMS Full GC:Serial Old 单线程回收,非常慢
  • G1 Full GC:JDK 10 之前单线程,之后多线程,但仍应尽量避免
  • 生产建议用 G1(JDK 11+ 默认),高吞吐场景可考虑 ZGC

详见 垃圾收集器

相关链接