OOM异常类型总结

鼠扑  金牌会员 | 2024-5-17 01:43:19 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 933|帖子 933|积分 2799

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发生时的堆内存状态。代码如下:
  1. /**
  2. * VM options: -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError
  3. * @author yywf
  4. * @date 2024/4/11
  5. */
  6. public class HeapOOMTest {
  7.     public static void main(String[] args) {
  8.         List<Object> list = new LinkedList<>();
  9.         while (true) {
  10.             list.add(new Object());
  11.         }
  12.     }
  13. }
复制代码
实行效果

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

字符串常量池溢出

通过String.intern()这个native方法将字符串添加到常量池中。
测试代码如下:
  1. /**
  2. * VM options: -Xms2M -Xmx2M
  3. * @author yywf
  4. * @date 2024/4/11
  5. */
  6. public class StringConstantOOMTest {
  7.     public static void main(String[] args) {
  8.         List<String> list = new LinkedList<>();
  9.         int i = 0;
  10.         while (true) {
  11.             list.add(String.valueOf(i++).intern());
  12.         }
  13.     }
  14. }
复制代码
实行效果

在jdk8中,字符串常量池已经移到了堆中。所以抛出的异常是堆内存溢出。
栈溢出

在JVM规范中,栈有捏造机栈和本地方法栈之分。但在现实的实现中,HotSpot捏造机是没有区分捏造机栈和本地方法栈的。所以对于HotSpot来说,-Xoss(设置本地方法栈大小)参数是无效的,栈容量只能通过-Xss参数设置。
栈深度造成的溢出

在JVM规范中,如果线程请求的栈深度大于捏造机所允许的最大深度,将抛出StackOverflowError异常。测试代码如下:
  1. /**
  2. * VM options: -Xss128k
  3. * @author yywf
  4. * @date 2024/4/11
  5. */
  6. public class StackOOMTest {
  7.     private int stackLength = 1;
  8.     public void stackDeep() {
  9.         stackLength++;
  10.         stackDeep();
  11.     }
  12.     public static void main(String[] args) {
  13.         StackOOMTest test = new StackOOMTest();
  14.         try {
  15.             test.stackDeep();
  16.         } catch (Throwable e) {
  17.             System.out.println("栈深度:" + test.stackLength);
  18.             throw e;
  19.         }
  20.     }
  21. }
复制代码
实行效果

创建线程造成的内存溢出

另一种情况,呆板的RAM内存是固定的,如果不思量其他程序占用内存,那么RAM就由堆、方法区、程序计数器、捏造机栈和本地方法栈瓜分。通过不停的创建线程占满RAM的内存,会导致什么情况呢?测试代码:
  1. /**
  2. * VM options: -Xss10M
  3. * @author yywf
  4. * @date 2024/4/11
  5. */
  6. public class CreateThreadOOMTest {
  7.     public void stackOOMByThread() {
  8.         while (true) {
  9.             Thread thread = new Thread(() -> {
  10.                 while (true) {
  11.                     try {
  12.                         Thread.sleep(60000);
  13.                     } catch (InterruptedException e) {
  14.                         throw new RuntimeException(e);
  15.                     }
  16.                 }
  17.             });
  18.             thread.start();
  19.         }
  20.     }
  21.     public static void main(String[] args) {
  22.         CreateThreadOOMTest test = new CreateThreadOOMTest();
  23.         test.stackOOMByThread();
  24.     }
  25. }
复制代码
这里把栈的大小设置为了10M,也就是说创建一个线程最少必要10M的内存。可以更快的出现效果。
实行效果

抛出的是OutOfMemoryError。慎用慎用慎用,重要的事情说三遍,本人在测试的时候电脑死机了一会。得亏在线程的run方法中让线程睡眠了,否则cpu+内存双双阵亡。
方法区溢出

方法区大小在jdk1.7(包含)以前版本是通过-XXermSize-XX:MaxPermSize来设置的。在jdk8的实现叫做元空间(metaspace),通过-XX:MetaspaceSize=10M-XX:MaxMetaspaceSize=10M来设置其大小。
方法区存放的是类的信息,所以在运行时不停创建类就行。这里使用CGLib动态代理来生成类,可以添加以下maven依赖来使用CGLib:
  1. <dependency>
  2.     <groupId>org.springframework</groupId>
  3.     <artifactId>spring-core</artifactId>
  4.     <version>5.3.13</version>
  5. </dependency>
复制代码
测试代码如下:
  1. /**
  2. * VM options: -XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M
  3. * @author yywf
  4. * @date 2024/4/11
  5. */
  6. public class MetaSpaceOomTest {
  7.     public static void main(String[] args) {
  8.         while (true) {
  9.             Enhancer enhancer = new Enhancer();
  10.             enhancer.setSuperclass(Object.class);
  11.             enhancer.setUseCache(false);
  12.             enhancer.setCallback(new MethodInterceptor() {
  13.                 @Override
  14.                 public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
  15.                     return methodProxy.invokeSuper(o, args);
  16.                 }
  17.             });
  18.             enhancer.create();
  19.         }
  20.     }
  21. }
复制代码
实行效果

本机直接内存溢出

通过-XX:MaxDirectMemorySize=10M参数设置能申请的DirectMemory大小。如果不设置则默以为java堆的最大值。通过反射获取Unsafe实例,使用其来申请DirectMemory内存。
测试代码如下:
  1. /**
  2. * VM options: -Xmx10M -XX:MaxDirectMemorySize=10M
  3. * @author yywf
  4. * @date 2024/4/11
  5. */
  6. public class DirectOOMTest {
  7.     public static void main(String[] args) throws IllegalAccessException {
  8.         Field unsafeField = Unsafe.class.getDeclaredFields()[0];
  9.         unsafeField.setAccessible(true);
  10.         Unsafe unsafe = (Unsafe) unsafeField.get(null);
  11.         while (true) {
  12.             unsafe.allocateMemory(1048576);
  13.         }
  14.     }
  15. }
复制代码
实行效果


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

鼠扑

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表