1. 什么是 Full GC
Full GC(Full Garbage Collection,全量垃圾回收)是 JVM 垃圾回收中最重量级的一环。与只回收年轻代(Young Generation)的 Minor GC 不同,Full GC 会对整个堆(Heap)进行回收,包括年轻代、老年代(Old Generation)以及元空间(Metaspace,部分实现中)等区域。
Full GC 通常伴随着较长的 STW(Stop The World)停顿,即所有应用线程都会被暂停,直到垃圾回收完成。在堆内存较大或对象较多时,一次 Full GC 可能耗时数百毫秒甚至数秒。如果线上频繁发生 Full GC,会导致接口响应变慢、超时、吞吐量骤降,严重时甚至引发雪崩。
因此,理解 Full GC 的触发原因,并掌握一套系统的排查方法论,是每个 Java 后端开发者必备的技能。
2. Full GC 的根本原因
Full GC 并非无缘无故发生,其背后通常有以下几类根本原因:
2.1 老年代空间不足
这是最常见的原因。当老年代被占满,无法为新晋升的大对象或长期存活对象腾出空间时,JVM 会触发 Full GC。常见诱因包括:
- 大对象直接进入老年代(如大数组、大集合),且频率过高;
- 对象晋升阈值设置不合理,导致大量对象过早进入老年代;
- 缓存、连接池等长期存活对象过多,占满老年代。
2.2 元空间(Metaspace)不足
JDK 8 之后,方法区被元空间取代,使用本地内存。当加载的类过多、动态生成类(如 CGLIB、反射、热部署)过多时,元空间可能被撑满,从而触发 Full GC。
2.3 显式调用 System.gc()
代码中显式调用 System.gc() 会触发 Full GC(除非显式开启 -XX:+DisableExplicitGC)。一些框架或中间件(如 RMI、NIO 相关组件)在特定场景下也会触发显式 GC。
2.4 晋升失败(Promotion Failure)
Minor GC 之后,存活对象需要晋升到老年代,但老年代剩余空间不足,导致晋升失败,进而触发 Full GC。
2.5 内存泄漏
对象被无意中持有引用而无法被回收,导致老年代持续增长,最终频繁触发 Full GC。常见泄漏点包括:静态集合持有对象、未关闭的连接/流、ThreadLocal 使用不当等。
2.6 堆内存配置过小
JVM 堆内存设置过小,无法满足应用正常运行的内存需求,导致频繁 GC,包括 Full GC。
3. 排查方法论
面对频繁 Full GC,建议按照以下方法论逐步排查,避免盲目调参:
3.1 第一步:确认现象与影响面
- 通过监控平台(如 Prometheus + Grafana、Zabbix)查看 GC 频率、GC 耗时、堆内存使用率曲线;
- 确认 Full GC 发生的时间点是否与发版、流量高峰、定时任务重合;
- 记录 Full GC 期间的业务表现(超时率、错误率、RT)。
3.2 第二步:开启 GC 日志
在启动参数中加入 GC 日志,这是排查的基础:
bash
-Xlog:gc*:gc.log:time,uptime,level,tags
对于 JDK 8 及以下版本:
bash
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/path/to/gc.log
3.3 第三步:分析 GC 日志
- 统计 Full GC 频率与单次耗时;
- 观察 Full GC 前后各代内存的变化,判断是哪个区域空间不足;
- 结合
-XX:+PrintHeapAtGC查看 GC 前后的堆详情。
3.4 第四步:获取堆转储(Heap Dump)
当怀疑内存泄漏或对象异常堆积时,获取堆转储文件:
bash
jmap -dump:live,format=b,file=heap.hprof <pid>
注意:-dump:live 会先触发一次 Full GC,生产环境需谨慎,建议在低峰期操作。
3.5 第五步:分析堆转储
使用 MAT、VisualVM 等工具分析堆转储,定位大对象、泄漏嫌疑对象。
3.6 第六步:验证与优化
- 针对根因修复代码(如释放连接、清理缓存、修正 ThreadLocal);
- 必要时调整 JVM 参数(堆大小、晋升阈值、GC 收集器);
- 优化后持续观察,确认 Full GC 频率下降。
4. JVM 工具实战:快速定位问题
下面介绍几款常用 JVM 工具,以及如何利用它们快速定位 Full GC 的根因。
4.1 jstat:实时查看 GC 情况
jstat 是 JDK 自带的轻量级工具,可以实时查看 JVM 各代内存使用和 GC 统计:
bash
jstat -gcutil <pid> 1000 10
输出示例:
text
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 0.00 45.23 98.76 82.11 75.00 1234 5.678 89 12.345 18.023
重点关注 O(老年代使用率)和 FGC(Full GC 次数)、FGCT(Full GC 累计耗时)。如果 O 持续高位且 FGC 快速增长,说明老年代压力大。
4.2 jmap:查看堆内存与导出堆转储
查看堆内存概要:
bash
jmap -heap <pid>
导出堆转储:
bash
jmap -dump:format=b,file=heap.hprof <pid>
4.3 jstack:排查线程问题
虽然 jstack 主要用于线程分析,但在 Full GC 导致线程阻塞时,可以查看线程状态,确认是否有线程长时间处于等待或阻塞:
bash
jstack <pid> > thread_dump.txt
4.4 MAT(Memory Analyzer Tool):堆转储分析利器
MAT 是 Eclipse 出品的堆转储分析工具,非常适合定位内存泄漏。

使用步骤:
- 用 MAT 打开
heap.hprof文件; - 点击 Leak Suspects 报告,MAT 会自动分析最可能的内存泄漏点;
- 查看 Histogram,按对象大小排序,找出占用内存最大的对象;
- 对可疑对象右键 → Merge Shortest Paths to GC Roots,查看引用链,定位是谁持有引用导致无法回收。
MAT 的 Leak Suspects 报告会直接给出嫌疑对象及其引用链,是排查泄漏最高效的入口。
4.5 VisualVM:可视化监控与堆转储分析

VisualVM 是 JDK 自带的图形化监控工具(JDK 9 之后需单独下载),功能全面:
- 监控:实时查看 CPU、堆内存、类加载、线程数;
- GC 可视化:直观看到各代内存的波动曲线;
- 堆转储:一键生成堆转储,并内置 OQL 控制台,可编写查询语句筛选对象;
- 抽样器:对 CPU 和内存进行采样分析。
使用 VisualVM 连接本地或远程 JVM 后,切换到「监视」页签即可看到堆内存曲线;切换到「抽样器」可对内存进行采样,快速定位大对象。
4.6 JConsole:JMX 监控

JConsole 是 JDK 自带的 JMX 监控工具,通过 JMX 协议连接 JVM:
- 内存页签:查看堆与非堆内存的使用曲线,以及各内存池(Eden、Survivor、Old、Metaspace)的占用;
- GC 按钮:可手动触发 GC(生产环境慎用);
- MBean 页签 :可查看
java.lang:type=GarbageCollector等 MBean 的 GC 次数与耗时。
JConsole 适合快速查看内存池分布,判断是哪个区域压力大。
4.7 工具选型建议
| 场景 | 推荐工具 |
|---|---|
| 快速查看 GC 频率与老年代占用 | jstat |
| 导出堆转储 | jmap |
| 分析内存泄漏根因 | MAT |
| 可视化监控与堆转储分析 | VisualVM |
| 查看各内存池占用与 GC 统计 | JConsole |
| 线程阻塞排查 | jstack |
5. 总结
线上 JVM 频繁 Full GC 是一个典型的性能问题,排查的关键在于「先看日志、再抓堆、后分析」:
- 理解原理:Full GC 是对整个堆的回收,STW 停顿长,影响大;
- 定位根因:老年代不足、元空间不足、显式 GC、晋升失败、内存泄漏、堆配置过小是常见原因;
- 方法论:确认现象 → 开启 GC 日志 → 分析日志 → 获取堆转储 → 分析堆转储 → 验证优化;
- 善用工具:jstat 看实时 GC、jmap 抓堆、MAT 分析泄漏、VisualVM 可视化、JConsole 看内存池,组合使用能大幅缩短排查时间。
最后提醒一点:排查 Full GC 时不要一上来就盲目调大堆内存或更换 GC 收集器,先找到根因再动手,才能从根本上解决问题。建议在日常开发中就开启 GC 日志,并接入监控告警,做到「问题发生时手里有数据」,这样线上排查才能事半功倍。