← 中文资源

简体中文版本

Java GC overhead limit exceeded — 定位分配压力与泄漏

用 jstat、堆直方图与 GC 日志判断存活集是否逼近堆上限,先修泄漏和分配热点,再按实测数据调整堆大小。

阅读英文原文

现象

抛出 java.lang.OutOfMemoryError: GC overhead limit exceeded;报错前应用已长时间无响应;jstat 显示老年代接近占满且 Full GC 次数持续增加;CPU 被 GC 线程占满而吞吐骤降。

诊断

实时观察 GC 并查看堆组成:

jstat -gcutil 12345 1000 10
jcmd 12345 GC.heap_info
jmap -histo:live 12345 | head -25
java -XX:+PrintFlagsFinal -version | grep -E 'MaxHeapSize|G1HeapRegionSize|UseG1GC'

O 与 M 接近 100%、FGC 不断增长,说明老年代已满;YGC 增长很快而 survivor 占用低,通常意味着分配速率过高。直方图中某个业务类实例数随运行时间持续增长就是泄漏,字节数组特别多则可能是大响应缓冲。再开启 GC 日志确认 Full GC 后的存活内存与 -Xmx 的差距。

修复

先解决对象保留与分配源头:给缓存设置容量和过期时间、分页或流式处理大结果集、删除无限增长的静态集合、避免每请求复制大对象。然后依据实测存活集设置堆大小,并开启 GC 日志;只有在泄漏修复后才调暂停目标:

java -Xms4g -Xmx4g -Xlog:gc*:file=/var/log/app/gc.log:time,uptime:filecount=5,filesize=20m -jar app.jar

盲目增大 -Xmx 只会推迟失败,甚至让容器因超限被杀;-XX:-UseGCOverheadLimit 不会释放内存,不应使用。

验证

按相同负载复跑,老年代在每次回收后应明显下降,FGC 接近 0,GC 时间只占很小比例;强制 Full GC 后存活内存应远低于 -Xmx。同时确认 JVM 堆上限小于容器内存限制,使进程在真正耗尽时干净地抛出 OOM,而不是被操作系统杀死。