GC Overhead limit exceeded错误表明JVM将98%时间用于GC却仅回收不足2%内存,需通过GC日志识别Allocation Failure高频、Full GC间隔缩短且老年代占用率不变、新生代频繁无用回收等信号,结合堆转储分析Retained Heap及引用链,定位静态缓存、ThreadLocal未remove、资源未关闭等代码根源。

看 GC 日志里是否出现“GC overhead limit exceeded”提示,或者观察老年代(Old Gen)使用率持续逼近 100%、Full GC 频次陡增但每次回收量极少——这是 OOM 最典型的前兆。
关注老年代占用率(O 列)是否长期 >95%
用 jstat -gc 查看时,重点关注 O(Old Utilization)列:
- 如果 O 值在多次采样中稳定在 95%~99%,且不回落,说明老年代快满了,对象正在大量晋升或长期存活;
- 配合 YGC(年轻代 GC 次数)和 FGC(Full GC 次数):若 FGC 频繁(比如每分钟好几次),但每次只回收几 MB,就是严重预警;
- 特别注意 OU(Old Used)持续上涨、OC(Old Capacity)不变,说明没扩容空间,只能靠 Full GC 清理——而清理不动,就离 OOM 不远了。
识别 GC overhead 超限的明确信号
当 JVM 触发 GC overhead limit exceeded 时,日志里会直接打印该错误,但它不是突然出现的,之前已有迹可循:
- GC 日志中出现大量 GC pause,单次耗时明显变长(如从 20ms 升至 300ms+);
- 单位时间内 GC 时间占比飙升(例如 10 秒内 GC 耗时累计超 7 秒);
- 日志里反复出现 “GC (Allocation Failure)” 或 “GC (Metadata GC Threshold)”,但堆内存释放量极小(如只腾出 1~2MB);
- 加上 -XX:+PrintGCDetails 后,能看到类似 “GC paused 286.4ms” 后紧跟着 “used 1984M of 2048M” 的组合——这就是临界状态。
结合时间维度看 GC 行为恶化趋势
不要只截取一行日志,要拉长时间窗口(比如 5~10 分钟)观察变化节奏:
- YGC 间隔从几秒拉长到几十秒,说明年轻代也快撑不住了;
- FGC 间隔不断缩短,且每次触发后 OU 下降幅度越来越小(如第一次降 50MB,第三次只降 5MB);
- 日志末尾开始出现 “to-space exhausted” 或 “promotion failed”,表明对象晋升失败,被迫触发 Full GC;
- 如果用了 G1,留意 “Mixed GC” 频次增加但 “CSet 大小却在萎缩”,说明可回收区域已所剩无几。
别忽略 GC 日志之外的关键线索
GC 日志只是表象,需同步验证其他指标才能确认是否真要 OOM:
- 检查是否开了 -XX:+UseGCOverheadLimit(默认开启),它是触发该错误的开关;
- 用 jmap -histo:live <pid> 快速看当前存活对象分布,重点查 byte[]、HashMap、ArrayList、String 等常见泄漏载体;
- 对比系统内存使用:如果 top 显示进程 RES 很高,但 JVM 堆(-Xmx)并不大,可能是堆外内存(Metaspace、Direct Buffer)在膨胀;
- 查 dmesg | grep -i "killed process",排除被 Linux OOM-Killer 杀掉的可能——那不是 JVM 报的 OOM。

















