Skip to content

故障诊断-OutOfMemoryError ​

看在前面 ​

前言 ​

实际代码执行过程中经常遇到 OOM 相关异常,本篇内容基于 JDK13 总结各种 OOM 相关联内存区域。方便定位是哪一块内存区域的问题,然后做参数调优。

OutOfMemoryError 相关异常 ​

一般情况下,OutOfMemoryError 异常后会跟随「Java heap space」描述。具体的异常定位还需要参考调用链「at ...」部分来定位。

OutOfMemoryError 涉及的异常情况较多,可使用查找定位本节内容列出的异常情况。

Java 堆内存溢出 ​

可通过 -Xms20m -Xmx20m 设置堆大小。

java
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at io.gourd.java.jvm.oom.HeapOOM$OomObj.<init>(HeapOOM.java:19)
	at io.gourd.java.jvm.oom.HeapOOM.main(HeapOOM.java:27)

字符串常量池溢出 ​

自 JDK 7 起,原本存放在永久代的字符串常量池被移至 Java 堆之中。所以在 JDK 7 及以上版本,限制方法区的容量对该测试用例来说是毫无意义的。

永久代和元空间只是《Java 虚拟机规范》方法区规范的实现。如果还在纠结这些概念,可参考我 jvm 专栏的《运行时内存数据区域》内容

java
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.HashMap.resize(HashMap.java:704)
    at java.util.HashMap.putVal(HashMap.java:663)

// 或者

Exception in thread 「main」 java.lang.OutOfMemoryError: Java heap space 
    at java.base/java.lang.Integer.toString(Integer.java:440) 
    at java.base/java.lang.String.valueOf(String.java:3058)

方法区出现内存溢出 Metaspace 或者 PermGen space ​

当前的很多主流框架,如 Spring、Hibernate 对类增强时,都会使用到 CGLib 这类字节码技术。当增强的类越多,就需要越大的方法区以保证动态生成的新类型可以载入内存。

PermGen space 是 JDK8 前永久代的异常,Metaspace 是 JDK8 后的元空间异常,都是虚拟机规范方法区的标准实现

java
Caused by: java.lang.OutOfMemoryError: PermGen space  --- JDK8 以上 Metaspace
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:616)
  • -XX:MaxMetaspaceSize:设置元空间最大值,默认是-1,即不限制,或者说只受限于本地内存大小。

  • -XX:MetaspaceSize:指定元空间的初始空间大小,以字节为单位。达到该值就会触发垃圾收集来卸载类型,同时收集器会对该值做调整。如果释放了大量的空间,就适当降低该值。如果释放了很少的空间,那么在不超过-XX:MaxMetaspaceSize(如果设置了)的情况下,适当提高该值。

  • -XX:MinMetaspaceFreeRatio:作用是在垃圾收集之后控制最小的元空间剩余容量的百分比。可减少因为元空间不足导致的垃圾收集的频率。类似的还有-XX:Max-MetaspaceFreeRatio,用于控制最大的元空间剩余容量的百分比。

创建线程导致内存溢出 unable to create native thread ​

虚拟机的栈内存允许动态扩展,当扩展栈容量无法申请到足够的内存时,将抛出 OutOfMemoryError 异常

java
Exception in thread "main" java.lang.OutOfMemoryError: unable to create native thread

本地交换空间 request size bytes for reason. Out of swap space? ​

当本机堆分配失败且本机堆可能接近耗尽时,Java HotSpot VM 代码会报告这个明显的异常。

java
 java.lang.OutOfMemoryError: request size bytes for reason. Out of swap space?

直接内存溢出 allocateMemory ​

直接内存(Direct Memory)的容量大小可通过 -XX:MaxDirectMemorySize 参数来指定。根据异常链 allocateMemory 方法跟踪定位。

如果不去指定,则默认与 Java 堆最大值(由 -Xmx 指定)一致

java
Exception in thread "main" java.lang.OutOfMemoryError
    at sun.misc.Unsafe.allocateMemory(Native Method)
    at io.gourd.java.jvm.oom.DirectMemoryOOM.main(DirectMemoryOOM.java:25)

GC Overhead limit exceeded ​

Java 进程超出 98% 的时间在做垃圾回收工作,工作中恢复了不到 2% 的堆空间。

java
java.lang.OutOfMemoryError: GC Overhead limit exceeded

一般情况下:增加堆大小

也可以使用 -XX:-UseGCOverheadLimit 来关闭该异常

数组创建溢出 Requested array size exceeds VM limit ​

请求的数组大小超过 VM 限制,表示程序将创建一个大于堆大小的数组。

比如堆为 256M,程序试图创建一个 512M 的数组。

java
 java.lang.OutOfMemoryError: Requested array size exceeds VM limit

一般情况下增加堆大小或者控制数组大小。

本地方法分配失败 reason stack_trace_with_native_method ​

堆栈跟踪中顶部的帧是本机方法,那么这表明本机方法遇到了分配失败。

分配失败是在 Java 本机接口 (JNI) 或本机方法中检测到的,而不是在 JVM 代码中。

java
 java.lang.OutOfMemoryError: reason stack_trace_with_native_method

压缩类空间 Compressed class space ​

在 64 位平台上,类元数据的指针可以用 32 位偏移量表示 (UseCompressedOops,默认启用)。类元数据可用的空间量将固定在 CompressedClassSpaceSize

java
 java.lang.OutOfMemoryError: Compressed class space

一般情况下增加压缩空间-XX: CompressedClassSpaceSize=4g或者关闭使用压缩。

注意:类元数据不止一种,即对象头信息-类型指针和其他元数据。只有对象头信息-类型指针存储在压缩类空间中。其他元数据存储在 Metaspace 中。参考本专栏《对象的创建与访问过程》详细了解。

StackOverflowError 相关异常 ​

线程请求的栈深度大于虚拟机所允许的最大深度,将抛出 StackOverflowError 异常

栈容量只能由 -Xss 参数来设定。关于虚拟机栈和本地方法栈在《Java 虚拟机规范》中描述了两种异常:

  • 线程请求的栈深度大于虚拟机所允许的最大深度,将抛出 StackOverflowError 异常。

  • 虚拟机的栈内存允许动态扩展,当扩展栈容量无法申请到足够的内存时,将抛出 OutOfMemoryError 异常。参考「方法区出现内存溢出」。

#说明

相关测试代码参考-源码

实际生产过程中我们可以选择更多的工具用于运行监控、分析 dump 文件: