OOM是什么?英文全称为 OutOfMemoryError(内存溢出错误)。当程序发生OOM时,如何去定位导致异常的代码还是挺麻烦的。

要检查OOM发生的原因,首先需要了解各种OOM情况下会报的异常信息。这样能缩小排查范围,再结合异常堆栈、heapDump文件、JVM分析工具和业务代码来判断具体是哪些代码导致的OOM。笔者在此测试并记录以下几种OOM情况。

环境准备

  • jdk1.8(HotSpot虚拟机)
  • windows操作系统
  • idea开发工具

在idea上进行测试时,需要了解idea执行测试用例如何设置虚拟机参数(VM options)。如下图所示:

  1. 单击main方法的启动图标,选择修改运行配置

  2. 打开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规范中,如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。测试代码如下:

/**
* 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);
}
}
}

执行结果

OOM异常类型总结的更多相关文章

  1. java虚拟机(一)——内存管理机制与OOM异常

    一  java内存区域与内存溢出异常(OOM) 1)运行时数据区域划分        1.程序计数器(Program Conuter Register) 程序计数器是一块较小的内存空间,它是当前线程执 ...

  2. Android OOM异常解决方案

    一,什么是OOM异常: OOM(out of Memory)即内存溢出异常,也就是说内存占有量超过了VM所分配的最大,导致应用程序异常终止: 二,为什么会产生OOM异常呢? OOM异常是Android ...

  3. JVM探究之 —— OOM异常

    在Java虚拟机规范的描述中,除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OutOfMemoryError(下文称OOM)异常的可能.本节探究主要基于jdk1.8的内存结构. 1. Jav ...

  4. JVM OOM异常会导致JVM退出吗?

    出处:  https://mp.weixin.qq.com/s/8j8YTcr2qhVActLGzOqe7Q  https://blog.csdn.net/h2604396739/article/de ...

  5. OOM异常的发生原因

    一,jvm内存区域 1,程序计数器 一块很小的内存空间,作用是当前线程所执行的字节码的行号指示器. 2,java栈 与程序计数器一样,java栈(虚拟机栈)也是线程私有的,其生命周期与线程相同.通常存 ...

  6. 使用反射,查找WCF异常类型

    //使用System.Reflection,查找System.ServiceModel的异常类型        public void ConsoleException()        {      ...

  7. OOM异常产生的原因和处理方法

    一般而言,android中常见的原因主要有以下几个: 1.数据库的cursor没有关闭. 2.构造adapter没有使用缓存contentview. 3.调用registerReceiver()后未调 ...

  8. c#中常用的异常类型

    c#中异常捕获catch{}常用的异常类型 Exception 类   描述 SystemException 其他用户可处理的异常的基本类  ArgumentException 方法的参数是非法的  ...

  9. java中的异常类型以及区别????

    一.引言 根据JDK的文档我们能够找到异常所在的包:java.lang.Throwable中,Throwable是所有异常类的根类,error是错误,在java.lang.error中,而Except ...

  10. JAVA 异常类型结构分析

    JAVA 异常类型结构分析 Throwable 是所有异常类型的基类,Throwable 下一层分为两个分支,Error 和 Exception. Error 和 Exception Error Er ...

随机推荐

  1. 【Azure Developer】使用 Azure Python 查看 Azure 所有的 Alert rule

    问题描述 在Azure Alert 门户中,可以列举出所有Azure资源的Alert rule信息,如下图: 如果像通过Python SDK来获取所有的Alert Rule,有什么可以参考的代码吗? ...

  2. 【Azure API 管理】APIM关闭开发者门户的办法

    问题描述 APIM默认提供了开发者门户,可以让用户体验如何来调用接口.但如果不想开发这个功能的情况下,是否有办法关闭呢? 问题解答 答案是:开发人员门户是没有办法关闭的.但是作为另一种的代替方案,如自 ...

  3. TLS数据包重组

    TLS解密 参考以下链接:Wireshark 解密 TLS报文_在线tls解密-CSDN博客 总结: 配置环境变量 wireshark首选项配置 TLS解密例子 Frame 2700 Frame 27 ...

  4. 树莓派修改根文件系统为f2fs

    目录 前言 操作简述 我的实际操作步骤 1. 准备 2. 查看树莓派分区信息 3. 备份根分区 4. 格式化树莓派TF卡根分区为f2fs文件系统 5.恢复备份 前言 在TF卡.固态硬盘之类的nand存 ...

  5. hadoop集群环境搭建--双NameNode

    hadoop配置文件修改 个人配置文件压缩包地址: hadoop配置文件压缩包地址点此下载 tar -zxvf 你的压缩包路径/hadoop.tar.gz -C /usr/hadoop(你的hadoo ...

  6. C++ Qt开发:QHostInfo主机地址查询组件

    Qt 是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍如何运用QHo ...

  7. dnsmasq 本地局域网DNS服务器搭建

    项目背景 因为本地环境需要使用域名进行调试,需要DNS服务器 DNS 机器IP:192.168.5.249   dnsmasq 服务端部署 #01 关闭防火墙 systemctl stop firew ...

  8. Kotlin学习快速入门(12)—— 位运算符

    由于不懂pythod,最近拜托朋友研究下解密live2d模型的解密算法,朋友写出了Java的代码 之后我进行改版,在转为kotlin的时候,发现kotlin自动转换有些坑,以及kotlin中的位运算符 ...

  9. XMLSpringEclipseWebCache

    XMLSpringEclipseWebCache XMLSpy编辑工具中如何设置XML的DTD/XSD校验指向本地,而不要直接指向网络去? 前提:在不修改XML的条件下,修改XML的,不要这个,这个变 ...

  10. new vs malloc

    https://www.cnblogs.com/qg-whz/p/5140930.html 1.申请内存位置 new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函 ...