Skip to content
应用运行时异常排查 · 第 2 篇 / 共 4 篇
领域生产问题
专题应用运行时排障专题
当前序列应用运行时异常排查
阅读位置第 2 篇 / 共 4 篇当前专题第 1 个序列 / 共 3 个序列

生产问题:Full GC 频繁时怎么排查

线上一旦开始频繁 Full GC,往往意味着问题已经不只是“有点慢”,而是服务稳定性开始明显变差了。

业务通常会同时看到:

  • 接口抖动
  • 停顿时间变长
  • CPU 被 GC 抢走
  • 吞吐明显下降
  • 最后甚至 OOM 或服务雪崩

先说结论

Full GC 频繁时,最重要的是先回答三件事:

  • GC 后堆有没有明显回落
  • 哪一类对象一直留在堆里
  • 是代码层对象堆积,还是 JVM 参数本身不合理

这三件事里,最先要确认的通常是第一条:

  • GC 之后到底有没有“真的回收掉东西”

因为这几乎决定了后面排查是偏代码对象问题,还是偏配置与容量问题。

一个典型故障现场

比如一个 Java 服务平时 Full GC 很少,某次活动后开始出现:

  • Full GC 次数持续增加
  • 单次停顿从几十毫秒到几秒
  • CPU 占用也同步升高
  • 接口 RT 明显变差
  • 但进程还没直接挂

这时如果只看到“GC 很多”,是不够的。更重要的是判断:

  • GC 后堆回没回落
  • 回落后为什么又很快涨回去
  • 是某类对象一直活着,还是分配速率本身已经不合理

一、先看 GC 日志

先确认:

  • Full GC 频率
  • 单次停顿时间
  • GC 后内存是否回落

同时最好再看:

  • 年轻代和老年代的变化趋势
  • Full GC 前后老年代占比
  • 问题是突然发生,还是长期缓慢恶化

如果 GC 后堆还是一直很高,那就要高度怀疑:

  • 活对象很多
  • 或者存在泄漏

如果 GC 后能明显回落,但很快又被打满,则更像:

  • 分配速率过高
  • 突发流量或批量任务引起短时间对象暴涨

这两种方向,治理方式完全不同。

二、再看堆快照

可以抓取:

bash
jmap -dump:format=b,file=heap.hprof <pid>

然后配合 MAT 去看:

  • Histogram
  • Dominator Tree
  • Retained Heap

真正实战里,更建议先重点看:

  • 哪类对象最多
  • 哪类对象 retained heap 最大
  • 谁持有了这些对象
  • 是业务缓存、集合、ThreadLocal、结果集还是框架对象

很多 Full GC 问题,最后发现都不是神秘 JVM 黑盒,而是很朴素的对象留存。

三、先分清是“对象留存太多”还是“分配速率太猛”

1. 对象留存太多

更典型的信号是:

  • GC 后堆不怎么降
  • 老年代一直高
  • Heap Dump 里大对象或集合明显堆积

2. 分配速率太猛

更典型的信号是:

  • GC 后能回落
  • 但很快又被新对象打满
  • 高峰流量、批处理、导出、序列化类任务时更明显

这一步非常重要,因为很多人一看到 Full GC 就默认是“内存泄漏”,其实并不总是这样。

四、线上最常见的根因

常见并不神秘,反而很“朴素”:

  • 本地缓存没有上限
  • 集合无限增长
  • ThreadLocal 未清理
  • 大对象批量堆积
  • 一次查太多数据进内存

再补几类非常高频的:

1. 查询或导出一次拉太多数据

应用本来不是常驻对象有问题,而是一次业务操作临时吃了太多堆。

2. 缓存策略没边界

最常见的是:

  • 本地缓存无限增长
  • key 设计没失效策略
  • 批量缓存预热没有容量评估

3. ThreadLocal 和上下文对象没清

在线程池场景里尤其容易留下长期引用。

4. 消费、批量任务、报表任务叠加

平时没问题,一到高峰期或任务高峰就开始 Full GC。

5. 参数配置和实际负载不匹配

如果堆本来就偏小、代际比例不合理、容器内存边界太紧,也会更容易把问题提前放大。

五、不要一上来就只调参数

很多 Full GC 问题,最后根因不是参数,而是代码层对象模型有问题。

更稳妥的顺序是:

  1. 先看对象留存
  2. 再看分代压力
  3. 最后再判断是否需要调 JVM 参数

参数不是不能调,而是它通常是“在理解问题之后的收尾动作”,不是第一反应。

六、一个更实用的排查顺序

线上 Full GC 频繁时,我通常按这个顺序看:

  1. 先看 GC 日志,确认频率、停顿和回收后堆是否回落。
  2. 再判断是对象留存太多,还是分配速率太猛。
  3. 如果怀疑对象留存,抓 Heap Dump 看大对象和 retained heap。
  4. 如果更像流量或任务触发,再去看最近流量、批任务、导出、缓存预热。
  5. 最后才回到 JVM 参数、堆大小和容器资源边界。

这个顺序的好处是,不会把“代码对象问题”误处理成“多给点内存试试”。

一句话总结

Full GC 频繁时,真正关键的不是先问“GC 怎么调”,而是先搞清楚对象为什么回不去,或者为什么分配得这么快。

把“GC 后是否回落、对象留存、分配速率、参数边界”这几条线看清,Full GC 问题通常就能更快落到真正根因上。

延伸阅读相关文章优先当前专题,再补跨专题关联。
同一序列 · 顺着当前主线继续读线程池打满时怎么排查适合把 CPU、Full GC、线程池和接口超时这类应用层异常放在一组里看。应用运行时排障专题 · 应用运行时异常排查同一序列 · 顺着当前主线继续读接口 RT 飙升但 CPU 不高时怎么排查适合把 CPU、Full GC、线程池和接口超时这类应用层异常放在一组里看。应用运行时排障专题 · 应用运行时异常排查同专题其他序列 · 共享标签:线上排障、案例排障接口超时排查为什么要区分 RT 和 queue wait适合把线程死锁、CPU 高、Full GC、接口超时和 OOM 现场保留放在一条运行时排障主线上看。应用运行时排障专题 · 应用运行时故障的现场与根因收敛同专题其他序列 · 共享标签:线上排障、案例排障线程池积压时怎么判断是慢任务还是流量放大适合把线程死锁、CPU 高、Full GC、接口超时和 OOM 现场保留放在一条运行时排障主线上看。应用运行时排障专题 · 应用运行时故障的现场与根因收敛跨专题关联 · 共享标签:线上排障、案例排障磁盘打满后为什么删除文件不一定立刻生效适合把 Redis 抖动、MySQL 连接打满、MQ 积压、ES 发黄、ClickHouse 合并堆积和磁盘打满放在同一条基础设施排障主线上看。数据与基础设施排障专题 · 数据与基础设施故障的分层排查跨专题关联 · 共享标签:线上排障、案例排障大 Header、buffer、timeout 问题怎么排查适合把 location 匹配、缓冲区和负载均衡放在一起看。容器与站点部署专题 · Nginx 进阶路由与代理治理
继续阅读应用运行时异常排查当前序列第 2 篇 / 共 4 篇当前专题第 1 个序列 / 共 3 个序列
往前看
上一篇CPU 飙升时怎么排查回到当前序列上一章上一序列业务稳定性与治理实践从第 1 篇开始:支付回调幂等到底该怎么落地
往后看
下一篇线程池打满时怎么排查继续当前序列下一章下一序列应用运行时典型故障案例从第 1 篇开始:Redis 热 Key 突增时怎么止血和回查

把零散经验整理成可查、可复用、可持续更新的企业级知识门户