频繁 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: 什么情况下对象会直接进入老年代?
答案:
- 大对象(超过
-XX:PretenureSizeThreshold) - 年龄达到阈值(默认 15,
-XX:MaxTenuringThreshold) - Survivor 区装不下的对象
- 动态年龄判断:某个年龄以上的对象总大小超过 Survivor 一半
详见 垃圾回收算法。
Q3: CMS 和 G1 Full GC 有什么区别?
答案:
- CMS Full GC:Serial Old 单线程回收,非常慢
- G1 Full GC:JDK 10 之前单线程,之后多线程,但仍应尽量避免
- 生产建议用 G1(JDK 11+ 默认),高吞吐场景可考虑 ZGC
详见 垃圾收集器。