该错误是JVM因老年代内存泄漏发出的紧急预警,表现为GC耗时超98%却仅回收不足2%空间;需通过GC日志、jmap分析及代码配置双线优化来定位并解决。

这个错误不是内存真不够用,而是GC在“徒劳挣扎”:98%以上的时间花在回收上,却连2%的堆空间都挤不出来。它本质是JVM发出的紧急预警——老年代快被长期存活对象塞满,内存泄漏风险极高。
看懂GC日志里的关键信号
启用详细GC日志(-Xloggc:gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps),重点关注三类线索:
- Full GC频繁发生,且每次回收后老年代占用率仍高于85%
- Minor GC间隔越来越短,但每次只回收少量新生代对象
- GC日志中反复出现“concurrent mode failure”或“promotion failed”,说明对象晋升失败、老年代已无空间容纳
快速定位内存泄漏源头
别急着调参数,先用工具抓“活证据”:
- 用 jmap -histo:live <pid> 查看存活对象数量TOP 20,重点关注 byte[]、HashMap$Node、ArrayList、String 等高频泄漏载体
- 用 jmap -dump:format=b,file=heap.hprof <pid> 生成堆转储,用 VisualVM 或 Eclipse MAT 打开,按“Dominator Tree”排序,找 Retained Heap 最大的类
- 检查静态集合(如 static List、static Map)、缓存未设过期策略、监听器注册后未反注册、ThreadLocal 没有 remove() 等典型泄漏模式
代码与配置协同优化
单靠增大堆内存治标不治本,需双线并进:
立即学习“Java免费学习笔记(深入)”;
- 代码层:避免在循环中创建大数组或大字符串;用 WeakReference/SoftReference 管理缓存;数据库分页查询代替全量加载;流式处理替代一次性加载大文件
- JVM层:优先用 G1(-XX:+UseG1GC),设置合理停顿目标(-XX:MaxGCPauseMillis=200);适当调高老年代初始占比(-XX:InitialHeapOccupancyPercent=45);禁用该检查(-XX:-UseGCOverheadLimit)仅作临时诊断,不可上线使用
验证是否真正解决
修复后不能只看不报错,要确认GC行为回归健康:
- 观察GC日志:Full GC 次数显著下降,老年代占用率稳定在50–70%区间
- 监控应用指标:堆内存使用曲线不再持续爬升,波动趋于平缓
- 压力测试对比:相同请求量下,GC时间占比降至5%以下,吞吐量回升


















