Full GC 频繁通常是什么原因?怎么定位与处理?
结论先行:Full GC(FGC)频繁的本质只有一句话:老年代“快速被填满”或“回收效率太低”。高频原因包括堆配得偏小、对象过早晋升、大对象直入老年代、内存泄漏、元空间不足,以及代码里显式调用 System.gc()。处理顺序一定是“先量化取证,再对症下药”,而不是盲目加内存或换垃圾收集器。
常见原因与现象对照表
| 原因 | 典型现象 | 处理方向 |
|---|---|---|
| 堆整体偏小 | FGC 频繁、Old 区长期高水位 | 合理扩堆或降低对象存量 |
| 对象过早晋升 | Young 区偏小、Survivor 放不下 | 调 -Xmn 与晋升阈值 MaxTenuringThreshold |
| 大对象直入老年代 | 频繁超大数组/超大集合 | 代码层拆分对象、分批处理 |
| 内存泄漏 | Old 区只涨不降、曲线爬坡 | 导出快照,MAT 追 GC Roots |
| 元空间不足/频繁扩容 | 类加载异常、Metaspace 水位抖动 | 调大 MaxMetaspaceSize,查动态生成类 |
| 显式 System.gc() | FGC 频繁但堆水位不高 | 排查 RMI 定时 GC 等,必要时 DisableExplicitGC |
定位步骤
- 先用
jstat -gcutil <pid> 5000连续采样,记录 FGC 次数增速与 Old 区占比; - 用
jmap -histo:live <pid>看存活对象分布,锁定数量异常的类; - 画内存曲线:水位平稳但 FGC 多 → 参数或晋升问题;曲线持续爬坡 → 泄漏;
- 拿不准就导堆快照(隔一段时间导两份做对比),用 MAT 确认根因归属。
处理示例
# 采样 GC 数据(每 5 秒一次,共 10 次)
jstat -gcutil 12345 5000 10
# 查看存活对象 Top 类,定位大头对象
jmap -histo:live 12345 | head -30
常见追问 / 记忆点
- 追问:怎么区分“对象就是多”和“对象泄漏”?答:间隔导出两份堆快照,同一类对象数量只增不减即为泄漏。
- 追问:Metaspace 也会引发 Full GC 吗?答:会,元空间触发扩容或回收时可能伴随 Full GC,注意观察 GC 日志中的 class 相关信息。
- 记忆点:FGC 频繁 ≠ 堆不够大;先看 Old 区水位曲线,“参数止血 + 快照归因”才是完整答案。