堆溢出解决思路

OOM:Java 堆溢出 java.lang.OutOfMemoryError: Java heap space

现象:‑Xmx 设置的最大堆内存被占满,GC 反复回收,回收后内存依然不够,抛出堆溢出。

一、定位步骤(排查思路)

  1. 开启堆 dump,发生 OOM 自动导出堆快照

    -XX:+HeapDumpOnOutOfMemoryError
    -XX:HeapDumpPath=/xxx/heap.hprof

程序 OOM 时自动生成 hprof 文件,用 MAT(Memory Analyzer Tool)(Eclipse 开源的内存分析工具,专门分析 hprof 堆转储快照文件,用来排查 Java 内存泄漏、堆 OOM)分析。

  1. MAT 分析 hprof 做三件事 ① 看大对象、支配树:是谁持有大量对象,是谁的引用链不让对象 GC。 ② 看对象数量:哪个类实例数量异常暴涨。 ③ 找泄漏点:对象本应该被回收,但被 GC Roots 强引用死死抱住。

区分两种完全不同情况:

  1. 内存泄漏(Memory Leak):对象不再业务使用,但存在强引用,无法 GC,内存只涨不降。
  2. 内存溢出(Memory Overflow):业务本身就需要这么多对象,内存给小了,没有泄漏。

二、情况 1:内存泄漏(最常见)

对象不用了,还被强引用持有,无法 GC。

常见泄漏场景

  1. 集合(List/Map)全局 static,只 add 不 remove,全局集合不断累积对象。
  2. ThreadLocal 没有 remove ();线程池线程复用,ThreadLocalMap 还持有对象引用。
  3. 监听器、回调注册,注销失败,引用没释放。
  4. 内部类持有外部类引用;匿名内部类捕获外部对象。
  5. 缓存没有设置淘汰策略(没有 LRU、过期时间),无限膨胀。

解决:

  • 不用的对象手动清除集合元素;
  • ThreadLocal 用完必须 remove;
  • 缓存增加最大容量、过期淘汰(Caffeine、Guava Cache);
  • 及时注销监听器回调;
  • 避免长生命周期对象持有短生命周期对象。

三、情况 2:没有泄漏,业务确实需要大量对象(堆内存配置过小)

例子:一次性查询百万数据全部加载进 List,全部放到内存。

MAT 看对象都是业务正常需要的,没有不该存活的对象。

解决思路二选一:

  1. 调高‑Xmx 堆内存,机器物理内存允许的前提下加大堆;
  2. 不要一次性全加载到内存,做流式处理 / 分页:分批读取、游标分页,不要把全量数据装入 List;使用迭代器逐条处理。

坑:select * from table 查出几十万行封装进 List,直接打爆堆,属于业务写法问题,不是泄漏。

四、代码层面临时优化手段

  1. 大对象尽量方法内局部变量,方法执行结束,无其他引用就可以 GC;
  2. 大集合用完置为 list=null,帮助 GC Roots 解除引用(更多是可读性,JVM 会自动识别);
  3. 避免在循环内疯狂 new 大量对象;循环内尽量复用对象,减少对象创建(看业务)。

五、GC 角度分析

观察 GC 日志:

复制代码
-XX:+PrintGCDetails

如果看到:YGC 频繁,每次回收后可用内存很少;频繁 Full GC,内存几乎释放不下来 → 大概率内存泄漏。 如果一次性突增大量对象直接打满堆,回收效果尚可,只是瞬时需要内存大 → 业务数据量问题。

相关推荐
wuminyu26 分钟前
Java FFM处理网络读写事件源码剖析
java·linux·c语言·jvm·c++
剑指offer.7 小时前
Linux多任务-线程篇
java·jvm·算法
Zzzzmo_8 小时前
JVM 知识整理
jvm
戴西软件11 小时前
远程协同仿真是什么体验?
运维·jvm·人工智能·自动化·rpa
西峰u1 天前
Java多线程初阶完整总结|线程、锁、volatile、等待通知、常见案例
java·开发语言·jvm
H_老邪1 天前
JVM 遇到的问题-1.1
jvm
SKH.1 天前
Linux软件编程(5)线程
java·linux·jvm
liangbo71 天前
JVM规范第 2章:从 class 文件到运行时数据区
java·jvm
沐苏瑶1 天前
JVM-内存区域、垃圾回收与类加载机制
java·jvm
Mr. zhihao2 天前
死锁排查实战:JVM 唯一会“自动报案“的问题(场景 B5)
java·jvm·gc