堆溢出如何处理
堆溢出如何处理
堆溢出(OOM,OutOfMemoryError: Java heap space)不是单一问题,而是内存分配失败的结果。处理要分三步:定位 → 分析 → 解决。
一、先搞清楚:为什么会堆溢出?
常见原因只有三类:
1.1 内存确实不够
- 堆设置太小(
-Xmx) - 突发大流量 / 大对象
特征:
- GC正常,但就是不够用
1.2 内存泄漏
更危险⚠️
-
对象本该被回收,但还被引用
-
常见:
- 静态集合持有对象
- 缓存没有清理
- ThreadLocal 未 remove
- Listener / 回调未释放
特征:
- GC频繁,但内存一直涨
- 最终 OOM
1.3 大对象/大量对象
- 一次性加载超大数据
- 无限循环创建对象
特征:
- OOM来得很快
二、如何排查(核心重点🔥)
Step 1:开启 OOM 自动 Dump
1 | -XX:+HeapDumpOnOutOfMemoryError |
OOM时自动生成内存快照
Step 2:分析 dump 文件
常用工具:
- MAT (Memory Analyzer Tool)
- VisualVM
- JProfiler
重点看:
- 哪些对象最多?
- Histogram(对象数量排行)
- 谁引用了它?(关键)
- Dominator Tree(支配树)
找到:
“谁让它活着”
Step 3:看 GC 情况
工具:
1 | jstat -gc <pid> 1000 |
看:
- Old区是否持续增长
- Full GC 是否频繁
三、解决方案(按原因分类)
3.1 情况1:堆太小
直接调大:
1 | -Xms2g |
建议:
- Xms = Xmx(避免动态扩容)
3.2 情况2:内存泄漏(重点)
常见修复方式:
3.2.1 静态集合问题
1 | static List<Object> list = new ArrayList<>(); |
✔ 解决:
- 定期清理
- 使用弱引用(WeakReference)
3.2.2 ThreadLocal 泄漏
1 | threadLocal.set(obj); |
✔ 必须:
1 | threadLocal.remove(); |
3.2.3 缓存问题
- 无限制缓存(Map)
✔ 解决:
- LRU缓存(如 Caffeine)
- 设置过期时间
3.2.4 监听器/回调未释放
- Observer模式常见
✔ 解决:
- 手动 unregister
3.3 情况3:大对象/批处理问题
❌ 错误写法
1 | List<Data> list = loadAll(); |
✅ 正确方式
- 分页加载
- 流式处理(stream / iterator)
四、面试标准回答模板
可以直接背:
堆溢出一般从三方面排查:
1)是否堆空间设置过小
2)是否存在内存泄漏(如静态集合、ThreadLocal、缓存等)
3)是否存在大对象或大量对象创建排查时我会先开启 HeapDump,使用 MAT 分析内存快照,重点查看 Dominator Tree 找出 GC Root 引用链,再结合 GC 日志判断内存增长趋势。
最后根据具体原因进行优化,比如调整堆大小、修复引用链、或者优化数据加载方式。
五、一句话总结
OOM 本质:对象太多 + GC回不掉