OOM 异常类型汇总
OOM是什么?英文全称为 OutOfMemoryError(内存溢出错误)。当程序发生OOM时,如何去定位导致异常的代码还是挺麻烦的。
要检查OOM发生的原因,首先需要了解各种OOM情况下会报的异常信息。这样能缩小排查范围,再结合异常堆栈、heapDump文件、JVM分析工具和业务代码来判断具体是哪些代码导致的OOM。笔者在此测试并记录以下几种OOM情况。
环境准备
- jdk1.8(HotSpot虚拟机)
- windows操作系统
- idea开发工具
在idea上进行测试时,需要了解idea执行测试用例如何设置虚拟机参数(VM options)。如下图所示:
-
单击main方法的启动图标,选择修改运行配置
-
打开Add VM options,将JVM参数填在图示VM options处
堆溢出
Java堆是用来存储对象实例的,只要不断的创建对象,并保证对象不被GC回收掉,那么当对象占用的内存达到了最大堆内存限制,无法再申请到新的内存空间时,就会导致OOM。要让对象不被回收就需要保证GC Roots引用链可以到达该对象,此处采用了List来保持对对象的引用。并且设置参数-XX:+HeapDumpOnOutOfMemoryError打印OOM发生时的堆内存状态。代码如下:
/**
* VM options: -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError
* @author yywf
* @date 2024/4/11
*/
public class HeapOOMTest {
public static void main(String[] args) {
List<Object> list = new LinkedList<>();
while (true) {
list.add(new Object());
}
}
}
执行结果
提示信息为GC overhead limit exceeded。
使用JProfiler打开heapDump文件,可以看到启动类加载器中的java.util.LinkedList占用了92.3%的堆内存
字符串常量池溢出
通过String.intern()这个native方法将字符串添加到常量池中。
测试代码如下:
/**
* VM options: -Xms2M -Xmx2M
* @author yywf
* @date 2024/4/11
*/
public class StringConstantOOMTest {
public static void main(String[] args) {
List<String> list = new LinkedList<>();
int i = 0;
while (true) {
list.add(String.valueOf(i++).intern());
}
}
}
执行结果
在jdk8中,字符串常量池已经移到了堆中。所以抛出的异常是堆内存溢出。
栈溢出
在JVM规范中,栈有虚拟机栈和本地方法栈之分。但在实际的实现中,HotSpot虚拟机是没有区分虚拟机栈和本地方法栈的。所以对于HotSpot来说,-Xoss(设置本地方法栈大小)参数是无效的,栈容量只能通过-Xss参数设置。
栈深度造成的溢出
在JVM规范中,如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出*Error异常。测试代码如下:
/**
* VM options: -Xss128k
* @author yywf
* @date 2024/4/11
*/
public class StackOOMTest {
private int stackLength = 1;
public void stackDeep() {
stackLength++;
stackDeep();
}
public static void main(String[] args) {
StackOOMTest test = new StackOOMTest();
try {
test.stackDeep();
} catch (Throwable e) {
System.out.println("栈深度:" + test.stackLength);
throw e;
}
}
}
执行结果
创建线程造成的内存溢出
另一种情况,机器的RAM内存是固定的,如果不考虑其他程序占用内存,那么RAM就由堆、方法区、程序计数器、虚拟机栈和本地方法栈瓜分。通过不断的创建线程占满RAM的内存,会导致什么情况呢?测试代码:
/**
* VM options: -Xss10M
* @author yywf
* @date 2024/4/11
*/
public class CreateThreadOOMTest {
public void stackOOMByThread() {
while (true) {
Thread thread = new Thread(() -> {
while (true) {
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
thread.start();
}
}
public static void main(String[] args) {
CreateThreadOOMTest test = new CreateThreadOOMTest();
test.stackOOMByThread();
}
}
这里把栈的大小设置为了10M,也就是说创建一个线程最少需要10M的内存。可以更快的出现结果。
执行结果
抛出的是OutOfMemoryError。慎用慎用慎用,重要的事情说三遍,本人在测试的时候电脑死机了一会。得亏在线程的run方法中让线程睡眠了,不然cpu+内存双双阵亡。
方法区溢出
方法区大小在jdk1.7(包含)以前版本是通过-XX:PermSize和-XX:MaxPermSize来设置的。在jdk8的实现叫做元空间(metaspace),通过-XX:MetaspaceSize=10M和-XX:MaxMetaspaceSize=10M来设置其大小。
方法区存放的是类的信息,所以在运行时不断创建类就行。这里使用CGLib动态代理来生成类,可以添加以下maven依赖来使用CGLib:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.13</version>
</dependency>
测试代码如下:
/**
* VM options: -XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M
* @author yywf
* @date 2024/4/11
*/
public class MetaSpaceOomTest {
public static void main(String[] args) {
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Object.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return methodProxy.invokeSuper(o, args);
}
});
enhancer.create();
}
}
}
执行结果
本机直接内存溢出
通过-XX:MaxDirectMemorySize=10M参数设置能申请的DirectMemory大小。如果不设置则默认为java堆的最大值。通过反射获取Unsafe实例,使用其来申请DirectMemory内存。
测试代码如下:
/**
* VM options: -Xmx10M -XX:MaxDirectMemorySize=10M
* @author yywf
* @date 2024/4/11
*/
public class DirectOOMTest {
public static void main(String[] args) throws IllegalAccessException {
Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
while (true) {
unsafe.allocateMemory(1048576);
}
}
}
执行结果
推荐阅读
-
35 岁实现财务*,腾讯程序员手握2300万提前退休?-1000万房产、1000万腾讯股票、加上300万的现金,一共2300万的财产。有网友算了一笔账,假设1000万的房产用于自住,剩下1300万资产按照平均税后20-50万不等进行计算,大约花上26-60年左右的时间才能赚到这笔钱。也就是说,普通人可能奋斗一辈子,才能赚到这笔钱。在很多人还在为中年危机而惶惶不可终日的时候,有的人的35岁,就已经安全着陆,试问哪个打工人不羡慕?但问题是有这样财富积累必然有像样的实力做靠山。没有人可以不劳而获。 看到这里,肯定有人说,那么对于普通人来说,卷可能真就成了唯一的出路。但是卷也有轻松的卷,“偷懒”的卷法,对于程序员而言,刨除掉一时无法改掉的开会传统占用的大部分时间,如何把有限的时间和精力放在真正重要的架构设计、需求设计上,而不是重复的造*,编码、改bug、手动测试。因此在科技改变生活的今天,学会使用AI工具成为程序员们的必备技能。 以全栈式全自动的软件开发工具飞算SoFlu软件机器人为例,作为全球首款面向微服务架构设计和最佳实践的软件机器人,SoFlu软件机器人改变了原来手工编码的作业模式,通过可视化拖拽方式以及参数配置就能实现等同于编写复杂代码的业务逻辑,在设计业务逻辑时就完成了微服务应用开发,做到“业务即图,图即代码”,实现“软件开发,十倍提效”。 而作为飞算SoFlu软件机器人的一个重要组成部分,FuncGPT(慧函数)支持所有类型函数创建。通过自然语言描述Java函数需求,实时生成高质量、高可读性的Java函数代码。生成代码可直接复制到IDEA,或一键导入Java全自动开发工具函数库。 FuncGPT(慧函数)具有五大核心能力: ● 自然语言:通过自然语言即可生成函数,降低软件开发门槛。 ● 秒级函数开发:全自动秒级开发函数,秒级完成,效率千倍提升。 ● 一站式函数开发应用:函数生成即可直接应用,效率、安全有保障。 ● 质量可靠:生成的函数代码符合业界规范、具备良好的可读性,符合最优解。 ● 灵活生成:可以根据开发人员的具体需求生成并支持调整和修改。 以“将文件流转换为File对象”这个需求为例,以下是FuncGPT(慧函数)秒级生成的代码: / 类名:FileConverter // 函数名:convertStreamToFile // 函数功能:将文件流转换为File对象 // POM依赖包:无 import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; public class FileConverter { /** * 将文件流转换为File对象 * @param inputStream 文件流 * @param filePath 文件路径 * @param fileName 文件名 * @return 转换后的File对象 * @throws IOException 如果发生I/O异常 */ public static File convertStreamToFile(InputStream inputStream, String filePath, String fileName) throws IOException { File file = new File(filePath + File.separator + fileName); // 创建File对象 try (FileOutputStream outputStream = new FileOutputStream(file)) { // 创建文件输出流 byte buffer = new byte[1024]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { // 从文件流读取数据并写入文件 outputStream.write(buffer, 0, bytesRead); } } return file; // 返回转换后的File对象 } } // 函数示例 // 将文件流转换为File对象示例 // 入参:inputStream,文件流 // 入参:filePath,文件路径 // 入参:fileName,文件名 // 出参:file,转换后的File对象 // 调用示例: // InputStream inputStream = new FileInputStream("example.txt"); // String filePath = "C:\\Users\\User\\Documents"; // String fileName = "example.txt"; // File file = FileConverter.convertStreamToFile(inputStream, filePath, fileName); // System.out.println(file.getAbsolutePath); // 输出结果:例如,将文件流转换为File对象后,文件的绝对路径为:C:\Users\User\Documents\example.txt // 则输出结果为:C:\Users\User\Documents\example.txt 通过分析,不难发现以上代码:
-
OOM 异常类型汇总
-
引发 "System.OutOfMemoryException "类型的异常。
-
2440 堆栈实现类型、b 系列指令、汇编丢弃 c、c 调用汇编、切换操作模式、初始化异常向量表、中断处理、
-
前端常用数据类型汇总
-
测试类型大汇总(45 类)
-
异常检测算法分类汇总(含常用开源数据集)
-
四种常见的异常类型 try catch(情况代码)
-
Java 内存溢出 (OOM) 异常完全指南
-
springboot启动异常(NoSuchBeanDefinitionException:没有符合条件的 "*.BaseMapper "类型的 Bean)。