堆内存溢出(java.lang.outofmemoryerror: java heap space)本质是堆中存活对象持续增长,gc后仍无法腾出足够空间分配新对象;需从代码(查泄漏、优化生命周期)、配置(合理设-xms/-xmx、开启heapdump)、资源管理(分页/流式处理、缓存限流、复用对象)三方面协同解决。

堆内存溢出(java.lang.OutOfMemoryError: Java heap space)是 JVM 中最常遇到的 OOM 类型,本质是堆中存活对象持续增长,GC 后仍无法腾出足够空间分配新对象。解决它不能只靠调大参数,得从代码、配置、资源管理三方面入手。
确认是不是真泄漏,还是单纯配小了
先别急着改 -Xmx。打开 JVM 参数加 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump.hprof,复现问题后用 MAT 或 VisualVM 分析 dump 文件:
- 看 Dominator Tree,找占用 Top 3 的对象类型和引用链
- 检查是否大量 HashMap/ArrayList 持有未清理的数据(比如静态集合缓存、监听器未注销)
- 对比多个 dump 时间点,观察某类对象实例数是否持续上升——这是典型泄漏信号
修复常见泄漏模式
很多泄漏不是“写错”,而是设计疏忽。重点关注这几类:
-
静态集合持有业务对象:如 static List
cache = new ArrayList(); 每次 add 却不 remove - 资源未关闭:FileInputStream、Connection、Statement 等未在 try-with-resources 或 finally 中 close
- 内部类强引用外部类:非静态内部类会隐式持有外部类引用,若被线程池或回调长期持有,会导致整个外部实例无法回收
- ThreadLocal 泄漏:尤其在线程池场景下,ThreadLocal 变量未 remove(),导致 value 和 key(若为弱引用)残留
优化对象生命周期和数据加载
即使没泄漏,不合理使用也会压垮堆:
- 数据库查询避免
SELECT * FROM huge_table,改成分页或流式处理(JDBC 的setFetchSize(Integer.MIN_VALUE)+ ResultSet.next()) - 大文件上传/解析不用 byte[] 一次性读入,改用 InputStream + buffer 分块处理
- 缓存加大小限制和过期策略(如 Caffeine 的 maximumSize + expireAfterWrite)
- 避免在循环里反复 new 大对象(如 10MB byte[]),可复用或改用池化
合理设置 JVM 堆参数
参数是兜底手段,不是根治方案:
-
-Xms 和 -Xmx 设为相同值(如
-Xms2g -Xmx2g),避免运行时扩容抖动 - 堆大小不超过物理内存的 75%,预留空间给元空间、直接内存、OS 缓存
- 配合 GC 日志:
-Xlog:gc*:file=gc.log:time,uptime,level,tags,观察 GC 频率与回收效果 - 若日志显示 Full GC 后老年代仍居高不下,基本可判定是泄漏;若每次 GC 都能清掉大部分,只是单次请求数据量过大,则侧重业务优化










