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

堆内存溢出(java.lang.OutOfMemoryError: Java heap space)不是单纯“内存不够用”的表象问题,而是 JVM 堆中存活对象持续增长、GC 后仍无法释放足够空间的系统性表现。解决它必须紧扣 JVM 内存结构特点,从代码行为、运行配置、资源使用三个层面协同入手。
看懂堆结构,才能定位真问题
JVM 堆分为年轻代(Eden + Survivor)和老年代。对象通常在 Eden 区创建,经过几次 Minor GC 存活后进入老年代。如果老年代持续增长且 Full GC 后回收极少,大概率是内存泄漏;如果 Eden 频繁满、Minor GC 很多但对象很快死亡,可能是短生命周期对象暴增(如循环里反复 new 大数组)。
关键动作:
- 加参数开启 GC 日志:-Xlog:gc*:file=gc.log:time,uptime,level,tags,观察 GC 频次、耗时、各代回收前后占用
- 对比多次 GC 日志:若老年代使用量单向爬升,基本锁定泄漏;若每次 GC 后都能回落,更可能是堆配小或瞬时压力大
- 不要只看“用了多少”,要看“谁占着不放”——这需要堆转储分析
用堆转储(Heap Dump)揪出泄漏源头
光看日志不够,得看内存快照。先让 JVM 在 OOM 时自动导出:
- 启动时加:-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heap.hprof
- 复现问题后,用 MAT(Eclipse Memory Analyzer)打开 hprof 文件
- 重点看 Dominator Tree:排前三的对象类型是谁?它们的 Retained Heap(自身+所有被引用对象总大小)是否异常高?
- 点开可疑类,看 Path to GC Roots:谁在强引用它?是不是静态集合、未注销监听器、ThreadLocal 没 remove?
改代码比调参数更治本
很多堆溢出根本不是内存小,而是对象“该死不死”。常见硬伤:
-
静态集合无清理:比如
static Map<string byte> cache = new HashMap();</string>只 put 不 remove —— 改用 Caffeine 或 Guava Cache,设maximumSize和expireAfterWrite - 资源未关闭:FileInputStream、Connection、ResultSet 忘了 close —— 统一用 try-with-resources
- 内部类误持外部实例:非静态内部类被线程池长期持有 —— 改为静态内部类 + 显式传参,或用 WeakReference
-
大对象滥用:一次读 500MB 文件进 byte[] —— 改用 InputStream 分块处理;查千万级表不用 SELECT * —— 改分页或 JDBC 流式游标(
setFetchSize(Integer.MIN_VALUE))
参数设置要讲逻辑,不是越大越好
堆参数是兜底手段,不是根治药方:
-
-Xms 和 -Xmx 设成相等值(如
-Xms2g -Xmx2g),避免运行时扩容抖动 - 堆总大小不超过物理内存的 75%,给元空间、直接内存、OS 缓存留余地
- 别盲目堆到 8G+:过大会拉长 GC 停顿,尤其 CMS/G1 在大堆下可能反而更脆弱
- 配合 -XX:+UseG1GC(JDK9+ 默认)或 -XX:+UseZGC(低延迟场景),比 Parallel GC 更适应大堆
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











