欢迎关注微信公众号:互联网全栈架构
很多Java程序员都经常会碰到OOM,而这类问题往往比较难以解决,找到root cause并不是件容易的事情,我们今天就来看看OOM到底是什么?有哪些常见的OOM,初步的解决方法有哪些?
OOM是Out Of Memory的缩写,翻译过来就是内存溢出,在程序运行过程中,如果出现了OOM,就会抛出OutOfMemoryError异常。在JVM运行时数据区中,只有程序计数器这一个区域不会出现OOM异常:
常见的OOM有如下这些:
一、Java heap space:可能最常见的OOM异常,表示Java堆内存溢出,示例:
package com.sample.core.oom;
// 堆内存溢出
public class HeapOOM {
public static void main(String[] args) {
String[] str = new String[10000 * 10000];
}
}
然后指定VM选项,把最大堆内存设置为10M:
运行代码,就会出现OOM的异常:
可通过增加堆内存、分析代码找出分配过多对象等方式来解决。比如上面的代码,如果运行的VM选项Xmx设置为1000M,那么程序运行的时候就不会报错。
二、GC Overhead limit exceeded:表示垃圾回收一直在运行从而导致程序本身运行缓慢。在垃圾回收后,如果Java进行花费89%的时间用于GC且恢复不到2%的内存,那么就会抛出这种类型的OOM。示例:
package com.sample.core.oom;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
// 设置VM参数为-Xmx10M,可以很快看到效果
public class OverheadExceedOOM {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
Random random = new Random();
while(true){
map.put(random.nextInt(), "互联网全栈架构");
}
}
}
对于这种类型的OOM,可以通过增加堆内存大小来解决,也可以添加这个VM参数来禁用这个检查:-XX:-UseGCOverheadLimit,当然,这个不会真正解决问题,只是把问题延后,最终还是出现Java heap space的OOM。当然,彻底的解决办法还是分析代码,看看到底是哪里导致了这样的问题。
三、Requested array size exceeds VM limit:顾名思义,这个错误表示数组大小超过了VM的限制,像这种OOM相对比较少见,一般情况下,也很少会声明一个数组,用于存放上亿个元素。
四、Metaspace:Java类的元数据(Java类在虚拟机中的内部表示)在堆外内存进行分配,如果存入Java类元数据的元空间用尽了,那么就会抛出这个错误,可以用参数MaxMetaSpaceSize来指定元空间的最大值。示例:
package com.sample.core.oom;
public class MetaspaceOOM {
static javassist.ClassPool cp
= javassist.ClassPool.getDefault();
public static void main(String args[]) throws Exception {
// 循环加载类
for (int i = 0; i < 100000; i++) {
Class c = cp.makeClass("com.sample.core.oom.MetaspaceOOM" + i)
.toClass();
}
}
}
Javassist是一个开源的分析、编辑和创建Java字节码的类库。使用它需要在pom.xml中引入以下的依赖:
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.28.0-GA</version>
</dependency>
指定VM的参数-XX:MaxMetaspaceSize=10M,运行代码,可以看到以下的OOM:
五、unable to create new native thread:当前JVM申请创建一个新的线程,但底层操作系统无法分配新的OS线程时,会抛出这样的异常。示例:
package com.sample.core.oom;
public class NewThreadOOM {
public static void main(String[] args) {
while(true){
new Thread(() -> {
System.out.println(Math.random() +" ");
try {
Thread.sleep(10000000);
} catch(InterruptedException e) { }
}).start();
}
}
}
由于操作系统的差异,以上代码在Linux平台上进行测试,运行一段时间后,会出现以下的异常:
六、request size bytes for reason. Out of swap space:当本地内存不够用时会抛出此异常,可通过日志文件排查问题的根本原因。通常,这个异常跟底层的操作系统有关系,比如没有配置足够的交换空间,或者别的进程消耗了所有的内存资源。
七、Compressed class space:在64位平台上,可以使用32位的偏移指针来访问数据,它通过参数UseCompressedClassPointers来进行控制(参数默认是开启的),如果它所需的空间超过了参数CompressedClassSpaceSize指定的值,就会抛出Compressed class space的OOM异常。
八、reason stack_trace_with_native_method:在JNI(Java Native Interface)或者本地方法遇到了资源分配失败时,会抛出此异常。
以上就是关于OOM的基本介绍,以及OOM的常见类型,对于最常遇到的情况,文章给出了样例代码进行演示,后续文章会继续介绍OOM的一些排查手段等内容,敬请期待。
都看到这里了,请帮忙一键三连啊,也就是点击文末的在看、点赞、分享,这样会让我的文章让更多人看到,也会大大地激励我进行更多的输出,谢谢!
鸣谢:
https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/memleaks002.html
https://www.geeksforgeeks.org/understanding-outofmemoryerror-exception-java/
周志明.《深入理解Java虚拟机》.机械工业出版社,2011
推荐阅读: