首页 > 其他分享 >OOM异常类型总结

OOM异常类型总结

时间:2024-04-11 23:55:58浏览次数:22  
标签:总结 OOM options XX 内存 new 异常 public

OOM是什么?英文全称为 OutOfMemoryError(内存溢出错误)。当程序发生OOM时,如何去定位导致异常的代码还是挺麻烦的。
要检查OOM发生的原因,首先需要了解各种OOM情况下会报的异常信息。这样能缩小排查范围,再结合异常堆栈、heapDump文件、JVM分析工具和业务代码来判断具体是哪些代码导致的OOM。笔者在此测试并记录以下几种OOM情况。

环境准备

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

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

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

  2. 打开Add VM options,将JVM参数填在图示VM options处
    image

堆溢出

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());
        }
    }
}

执行结果
image
提示信息为GC overhead limit exceeded。
使用JProfiler打开heapDump文件,可以看到启动类加载器中的java.util.LinkedList占用了92.3%的堆内存
image

字符串常量池溢出

通过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());
        }
    }
}

执行结果
image

在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;
        }
    }
}

执行结果
image

创建线程造成的内存溢出

另一种情况,机器的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的内存。可以更快的出现结果。
执行结果
image

抛出的是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();
        }
    }
}

执行结果
image

本机直接内存溢出

通过-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);
        }
    }
}

执行结果
image

标签:总结,OOM,options,XX,内存,new,异常,public
From: https://www.cnblogs.com/yywf/p/18130286

相关文章

  • 软件评测师(中级)|上午选择题总结
    1.进制转换1.1.十进制转换为其他进制1.2其他进制转换为十进制 2.存储单位位bit字节Byte字字长常用的单位(存储)1B=8bit1KB=2^10B=1024B1MB=2^10KB=2^20B=1024*1024B1GB=2^10MB=2^20KB=2^30B=1024*1024*1024B1TB=2^10GB=2^20MB=2^30KB=2^40B=1024*1024*1024*......
  • 关于git的上传远程仓库的常用命令总结
    我一般上传的步骤:(注意在那个文件夹下打开你要传送的项目)(1)gitinit(2)gitadd.(3)gitcommit-m"上传的项目"(4)与你需要的仓库建立连接gitremoteaddorigin(SSH/HTTPS)(5)gitpush-uoriginmaster/main出现发现远程仓库需要删除的文件下如何进行修改.........
  • 最长递增子序列leetcode的总结
    使用动态规划解决,首先明白dp数组的含义dp[i]表示在位置i时最长的递增子序列dp[i]=max(dp[j]+1,dp[i])为递推公式初始化dp[i]=1都初始化为1因为最基本的每一个位置至少为一个遍历顺序for(inti=2;i<len;i++){            for(intj=0;j<i;j++){if(n......
  • const 使用总结
    const关键字是用于定义一个不该被改变的对象,它的作用是告诉编译器和其他程序员不允许修改这个对象的值或者状态。当程序员看到使用const修饰的代码时就知道不应该修改对应对象的值,而编译器则会强制实施这个约束,任何违反这个规定的代码会在编译期间报错。它可以用于任何函数或者类......
  • 腾讯云轻量服务器月流量用尽怎么办?解决方案及优惠活动总结
    随着云计算的普及,越来越多的用户选择了腾讯云的轻量应用服务器。其中,月流量是一个重要的考量指标。那么,腾讯云轻量服务器的3500G月流量意味着什么呢?简单来说,这是指在一个月内,从您的轻量服务器流出的数据总量上限为3500GB。对于大多数用户而言,3500G的月流量是相当充裕的,正常使......
  • 第六章总结
    网络Api微信小程序处理的数据通常从后台服务器获取,再将处理过的结果保存到后台服务器,这就要求微信小程序要有与后台进行交互的能力。微信原生API接口或第三方API提供了各类接口实现前后端交互。网络API可以帮助开发者实现网络URL访问调用、文件的上传和下载、网络套接字的......
  • document 常用属性方法总结
    attrbutenodeName定义:nodeName属性返回节点的名称使用console.log(div1.nodeName)nodeValue定义:nodeValue属性返回一个字符串,表示当前节点本身的文本值,该属性可读写只有文本节点(text)、注释节点(comment)和属性节点(attr)有文本值.使用console.log(div1.childNodes[1].nod......
  • Typescript 详细总结1
    基本数据类型类型注释:numberlet num:number=5//数字类型let str:string='str'//字符串类型let boolen:boolean =true//布尔 类型断言,确定类型后,不能赋其他类型值any类型let  anything:any='ssssss'let  anything:any=223any类型......
  • localStorage使用总结
    一、什么是localStorage、sessionStorage在HTML5中,新加入了一个localStorage特性,这个特性主要是用来作为本地存储来使用的,解决了cookie存储空间不足的问题(cookie中每条cookie的存储空间为4k),localStorage中一般浏览器支持的是5M大小,这个在不同的浏览器中localStorage会有所不同......
  • 软工第二次任务-工作总结
    引言:在软件开发的广阔天地中,单元测试是确保代码质量和功能正确性的关键步骤。它不仅有助于及时发现和修复缺陷,还能提高开发效率,减少后期维护成本。本次任务旨在对软件工程中的单元测试进行全面的总结,以期为未来的开发工作提供宝贵的经验和参考。一、单元测试的重要性单元测试,顾......