堆溢出如何处理

堆溢出(OOM,OutOfMemoryError: Java heap space)不是单一问题,而是内存分配失败的结果。处理要分三步:定位 → 分析 → 解决

一、先搞清楚:为什么会堆溢出?

常见原因只有三类:

1.1 内存确实不够

  • 堆设置太小(-Xmx
  • 突发大流量 / 大对象

特征:

  • GC正常,但就是不够用

1.2 内存泄漏

更危险⚠️

  • 对象本该被回收,但还被引用

  • 常见:

    • 静态集合持有对象
    • 缓存没有清理
    • ThreadLocal 未 remove
    • Listener / 回调未释放

特征:

  • GC频繁,但内存一直涨
  • 最终 OOM

1.3 大对象/大量对象

  • 一次性加载超大数据
  • 无限循环创建对象

特征:

  • OOM来得很快

二、如何排查(核心重点🔥)

Step 1:开启 OOM 自动 Dump

1
2
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/path/heapdump.hprof

OOM时自动生成内存快照

Step 2:分析 dump 文件

常用工具:

  • MAT (Memory Analyzer Tool)
  • VisualVM
  • JProfiler

重点看:

  1. 哪些对象最多?
  • Histogram(对象数量排行)
  1. 谁引用了它?(关键)
  • Dominator Tree(支配树)

找到:

“谁让它活着”

Step 3:看 GC 情况

工具:

1
jstat -gc <pid> 1000

看:

  • Old区是否持续增长
  • Full GC 是否频繁

三、解决方案(按原因分类)

3.1 情况1:堆太小

直接调大:

1
2
-Xms2g
-Xmx2g

建议:

  • 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回不掉