先看错误信息末尾后缀定位内存区域:Java heap space(堆)、Metaspace(类元数据)、Direct buffer memory(直接内存)、unable to create new native thread(线程数超限)或GC overhead limit exceeded(GC失效),再结合堆转储分析、参数调优与代码修复。
遇到 outofmemoryerror,别急着调大堆内存。先看错误信息末尾那行字——它直接告诉你问题出在哪块“地”上,这才是排查的起点。
一、看错因,分清是哪块内存告急
不同后缀代表不同区域失守,处理思路完全不同:
- Java heap space:堆内存不够用。常见于对象堆积、缓存无界、一次查几百万条数据库没分页。
- Metaspace(Java 8+)或 PermGen space(Java 7 及以前):类太多装不下。典型场景是热部署频繁、用 CGLib/ASM 动态生成代理类、Spring Boot 多模块反复加载。
-
Direct buffer memory:NIO 直接内存爆了。比如大量使用
ByteBuffer.allocateDirect()但没显式清理,或未配-XX:MaxDirectMemorySize。 -
unable to create new native thread:不是内存不够,是系统线程数到上限了。可能线程池没设界、ThreadLocal 没清理、或
-Xss设得过大导致单个线程吃太多栈空间。 - GC overhead limit exceeded:GC 已经拼命干活(98%时间在回收),却只挤出不到 2%空间。说明老年代快满了,且对象长期存活,大概率是内存泄漏前兆。
二、抓现场,让 JVM 自己留证据
别等下次 OOM 才行动。现在就给 JVM 下指令,让它出事时自动存档:
- 启动参数加:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/dump/,OOM 时自动生成.hprof文件。 - 顺手加上:
-XX:+PrintGCDetails -Xloggc:/opt/logs/gc.log,记录每次 GC 行为,看是不是越收越慢、越收越少。 - 运行中想临时抓一把快照?执行:
jmap -dump:format=b,file=heap.hprof <pid>。
三、查堆转储,定位谁在“占着茅坑”
拿到 .hprof 文件后,用 Eclipse MAT 打开:
- 先跑 Leak Suspects Report,它会标出几个最可疑的“大户”。
- 切到 Dominator Tree,按 Retained Heap 排序——这个值才是关键,表示“删掉它能腾出多少内存”,比对象自身大小(Shallow Heap)更有意义。
- 对高占比类右键 → Path to GC Roots → 勾选 exclude all weak/soft/phantom references,看是谁用强引用死死拽着它不放。常见元凶:静态集合、未注销的监听器、ThreadLocal 持有业务对象、缓存没设过期或淘汰策略。
四、动配置,该扩容的扩容,该限流的限流
代码没毛病,但负载真上来了,就得靠参数兜底:
立即学习“Java免费学习笔记(深入)”;
- 堆内存:设
-Xms2g -Xmx2g(大小一致),避免运行中伸缩抖动;新生代比例按对象生命周期调,如-XX:NewRatio=3(老:新 = 3:1)。 - 元空间:加
-XX:MaxMetaspaceSize=512m,防动态类无限膨胀;观察是否需同步调大-XX:MetaspaceSize避免频繁触发初始 GC。 - 直接内存:若用 NIO,务必配
-XX:MaxDirectMemorySize=1g,和堆一样要设上限。 - 线程栈:默认
-Xss1m,线程多时可降到512k,但注意别引发StackOverflowError。
不复杂但容易忽略:很多 OOM 其实不是内存真不够,而是某处引用没断、资源没关、缓存没控。日志里那行报错,就是第一张地图。


















