应先诊断内存问题根源而非盲目调大-Xmx:通过GC日志、jstat查老年代增长趋势、jmap分析对象分布,区分是流量突增、配置不足还是内存泄漏;再结合MAT分析堆转储定位静态集合、ThreadLocal等泄漏源。

直接调高堆内存参数往往只能缓解,不能根治。关键得先判断是配置不足、流量突增,还是代码真有泄漏——三类原因对应不同处理路径。
看日志和监控,快速定位类型
收到 java.lang.OutOfMemoryError: Java heap space 后,别急着改 -Xmx。先查两件事:
- 错误是否集中在某次活动/上线后突然出现?比如秒杀开始5分钟内爆发 → 很可能是业务流量超预期;
- 错误是否随运行时间推移越来越频繁,且 Full GC 后堆内存回收极少?→ 倾向于内存泄漏;
- 错误发生前是否有单次大对象分配(如 new byte[1024*1024*500])?→ 可能是超大对象申请失败。
按场景调整 JVM 参数
参数不是越大越好,要匹配实际压力:
- 日常服务:设 -Xms 和 -Xmx 相等(如
-Xms2g -Xmx2g),避免堆动态扩容带来的停顿; - 批处理任务:可临时加大堆,例如
-Xmx4g,但需配合 -XX:+HeapDumpOnOutOfMemoryError 自动导出堆快照; - 容器环境:必须设置 -XX:+UseContainerSupport,否则 JVM 可能无视容器内存限制,导致 OOM 被 kill。
用工具确认泄漏点,不止靠猜
光看代码很难发现隐藏引用。推荐组合使用:
立即学习“Java免费学习笔记(深入)”;
- 线上轻量排查:加 -XX:+PrintGCDetails -Xloggc:gc.log,观察 GC 频率与回收量比值;
- 定位对象堆积:用 jstat -gc <pid> 查看老年代使用率是否持续攀升;
- 深度分析堆快照:用 Eclipse MAT 打开 hprof 文件,重点关注 “Dominator Tree” 和 “Leak Suspects” 报告;
- 常见泄漏源:静态集合未清理、ThreadLocal 没 remove、监听器注册后未反注册、缓存未设淘汰策略。
代码层规避高频风险
很多 OOM 其实源于几个固定写法习惯:
- 数据库查询不加 limit 或分页,一次拉几十万条进内存;
- 用 static Map<String, Object> 做缓存,却从不清理过期项;
- 流式处理时,把 InputStream 转成 byte[] 而非用缓冲区逐段读;
- 日志打印对象时写
log.info("obj={}", hugeObject),触发 toString() 加载全部字段。


















