前言

java OOM的种类整理

1、分类

1.1【情况一】

1
java.lang.OutOfMemoryError: Java heap space:

这种是java堆内存不够,一个原因是真不够,另一个原因是程序中有死循环
如果是java堆内存不够的话,可以通过调整JVM下面的配置来解决:

-Xms3062m
-Xmx3062m

  

1.2【情况二】

1
java.lang.OutOfMemoryError: GC overhead limit exceeded

【解释】

JDK6新增错误类型,当GC为释放很小空间占用大量时间时抛出;一般是因为堆太小,导致异常的原因,没有足够的内存。

【解决方案】

1、查看系统是否有使用大内存的代码或死循环;
2、通过添加JVM配置,来限制使用内存:

-XX:-UseGCOverheadLimit

  

1.3【情况三】

1
java.lang.OutOfMemoryError: PermGen space:

这种是P区内存不够,可通过调整JVM的配置:

-XX:MaxPermSize=128m
-XX:PermSize=128m

【注】

JVM的Perm区(永久代)主要用于存放Class和Meta信息的,Class在被Loader时就会被放到PermGen space。注意永久代是独立于新生代/年老代的第三个区域(存放类元数据),并不等同于存放对象实例的年老代;GC在主程序运行期间不会对Perm区进行清理,默认是64M大小,当程序需要加载的类比较多时,超过64M就会报这部分内存溢出了,需要加大内存分配,一般128m足够。

版本提示:JDK 8 起永久代已被移除,类元数据改存于本地内存的 Metaspace(受 ‑XX:MaxMetaspaceSize 限制,默认无上限),JDK 8+ 环境不会再出现 PermGen space 溢出,对应的是情况七。   

1.4【情况四】

1
java.lang.OutOfMemoryError: Direct buffer memory 

调整-XX:MaxDirectMemorySize= 参数,如添加JVM配置:

-XX:MaxDirectMemorySize=128m

  

1.5【情况五】

1
java.lang.OutOfMemoryError: unable to create new native thread 

【原因】

Stack空间不足以创建额外的线程,要么是创建的线程过多,要么是Stack空间确实小了。

【解决】

JVM没有提供参数设置总的stack空间大小,但可以设置单个线程栈的大小;而32位系统的用户进程空间一共约3G(64位系统进程空间大得多,此问题主要困扰32位环境),除了Text/Data/BSS /MemoryMapping几个段之外,Heap和Stack空间的总量有限,是此消彼长的。因此遇到这个错误,可以通过两个途径解决:
1.通过 -Xss启动参数减少单个线程栈大小,这样便能开更多线程(当然不能太小,太小会出现StackOverflowError);
2.通过-Xms -Xmx 两参数减少Heap大小,将内存让给Stack(前提是保证Heap空间够用)。   

1.6【情况六】

1
java.lang.StackOverflowError 

【原因】

这也内存溢出错误的一种,即线程栈的溢出,要么是方法调用层次过多(比如存在无限递归调用),要么是线程栈太小。

【解决】

优化程序设计,减少方法调用层次;调整-Xss参数增加线程栈大小。

1.7【情况七】

1
java.lang.OutOfMemoryError: Metaspace

【原因】

JDK 8+ 使用 Metaspace 取代了永久代,类元数据存放在本地内存(Native Memory)中。出现该错误一般是因为动态生成类的场景过多(如大量反射、CGLIB 动态代理、Groovy/热部署),或没有限制 Metaspace 大小时物理内存被耗尽。

【解决】

通过 -XX:MaxMetaspaceSize 显式限制 Metaspace 大小避免拖垮物理内存(如 -XX:MaxMetaspaceSize=256m);同时排查是否有类加载器泄漏(重复部署的应用未卸载旧类)。注意 Metaspace 也可能伴随 OutOfMemoryError: Compressed class space(-XX:CompressedClassSpaceSize 可调)。

补充:JDK 8+ 还有 OutOfMemoryError: Out of swap space(物理内存+交换空间耗尽,需从操作系统层面排查)与 Requested array size exceeds VM limit(数组长度超过 VM 上限,通常是程序 bug 而非调优问题)两种较少见的形态。

2、JDK 21 时代的形态对照

OOM JDK 7/8 时代的处理 JDK 21 时代的变化
Java heap space 调大 Xms/Xmx 处理思路不变;G1/ZGC 下优先看堆转储找泄漏源
GC overhead limit exceeded 关 -XX:-UseGCOverheadLimit(不建议) 该错误不再出现:它是 Parallel GC 专属的兜底检查,G1/ZGC 没有此机制,直接按 heap 泄漏排查
PermGen space 调 MaxPermSize 已随永久代消失,对应变为 Metaspace(见情况七)
Direct buffer memory 调 -XX:MaxDirectMemorySize 不变,Netty/堆外缓存场景常见
unable to create new native thread 减 Xss 或减堆腾内存 形态大变:JDK 21 虚拟线程(Executors.newVirtualThreadPerTaskExecutor())不 1:1 占用内核线程,百万级任务不再是问题;仅当载体平台线程或进程级线程数真打满时才报此错
Metaspace ——(当时不存在) 新增形态,见情况七

一句话:调参三板斧(PermSize/CMS/GC overhead 开关)在 JDK 21 上大多已失效或无意义,OOM 排查的重心从「调 JVM 参数」转向「堆转储分析 + 泄漏定位 + 虚拟线程改造」。

相关阅读