应先判断是配置偏低、流量突增还是内存泄漏,再针对性处理:瞬时流量看活动场景,泄漏看full gc后老年代不降,超大对象看显式分配;调参须配-xms=-xmx、heapdumponoom等,并用jstat、mat分析验证。

遇到 OutOfMemoryError: Java heap space,不能只靠调大 -Xmx 硬扛。参数调整必须配合问题类型判断——是配置偏低、流量突增,还是代码真有泄漏?盲目加堆可能掩盖问题,甚至让 GC 停顿更长、服务更卡。
先看现象,再定方向
打开日志和监控,快速归类原因:
- 错误集中在某次活动(如秒杀、报表导出)开始后几分钟内爆发 → 大概率是瞬时流量或大对象申请,比如一次性读取 500MB 文件或查出百万行数据
- 错误频率随运行时间推移越来越高,Full GC 后老年代内存几乎不下降 → 倾向于内存泄漏,如静态 Map 持有业务对象、ThreadLocal 未清理、监听器未注销
- 错误发生前有明确的大数组分配,例如
new byte[1024 * 1024 * 500]→ 属于单次超大对象分配失败,和堆总量关系不大,更需检查业务逻辑
按场景设对 JVM 参数
参数不是越大越好,要匹配实际运行环境:
-
日常在线服务:设
-Xms和-Xmx相等(如-Xms2g -Xmx2g),避免堆动态扩容带来的 STW 停顿 -
批处理任务:可临时加大堆(如
-Xmx4g),并务必加上-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/dump/,自动保留现场 -
容器环境(Docker/K8s):必须加
-XX:+UseContainerSupport,否则 JVM 可能读不到 cgroup 内存限制,导致被系统 OOM killer 杀掉
必须配套的诊断动作
光调参数不分析,等于蒙眼开车:
- 启用 GC 日志:
-Xlog:gc*:file=gc.log:time,uptime,level,tags(JDK 11+),观察是否频繁 Full GC、老年代增长是否持续 - 用
jstat -gc <pid></pid>实时查老年代使用率和 GC 次数,确认是否存在“回收不动”的迹象 - 拿到堆转储(.hprof)后,用 MAT 分析 dominator tree,重点看
HashMap、ArrayList、ThreadLocalMap、静态引用链下的对象实例
调参之外的关键动作
很多问题根本不在堆大小,而在代码和设计:
- 检查缓存:是否用了无过期、无容量限制的本地缓存(如
ConcurrentHashMap不加 size 控制) - 检查流式处理:数据库查询、文件读取是否用了分页或流式 API(如
Stream.iterate、JdbcTemplate.queryForStream),而非全量加载 - 检查第三方组件:某些 SDK 或中间件(如旧版 Druid、Logback 异步 Appender)存在已知内存泄漏,需升级版本
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











