异常处理机制】Java出现OutOf MemoryError(OOM 错误)的原因有哪些?出现OOM错误后,怎么解

2021-11-09 09:27发布

5条回答
爱梦 - 拿来吧你
2楼 · 2021-11-09 09:32

OutOf MemoryError这种错误可以细分为多种不同的错误,每种错误都有自身的原因和解决办法,

如下所示:java.lang.OutOfMemoryError: Java heap space 错误原因:此OOM是由于JVM中heap的最大值不满足需要。 

解决方法:1) 调高heap的最大值,即-Xmx的值调大。

2) 如果你的程序存在内存泄漏,一味的增加heap空间也只是推迟该错误出现的时间而已,所以要检查程序是否存在内存泄漏。


java.lang.OutOfMemoryError: GC overhead limit exceeded

错误原因:此OOM是由于JVM在GC时,对象过多,导致内存溢出,建议调整GC的策略,在一定比例下开始GC而不要使用默认的策略,或者将新代和老代设置合适的大小,需要进行微调存活率。 解决方法:改变GC策略,在老代80%时就是开始GC,并且将-XX:SurvivorRatio(-XX:SurvivorRatio=8)和-XX:NewRatio(-XX:NewRatio=4)设置的更合理。


java.lang.OutOfMemoryError: Java perm space

错误原因:此OOM是由于JVM中perm的最大值不满足需要。 解决方法:调高heap的最大值,即-XX:MaxPermSize的值调大。 另外,注意一点,Perm一般是在JVM启动时加载类进来,如果是JVM运行较长一段时间而不是刚启动后溢出的话,很有可能是由于运行时有类被动态加载进来,此时建议用CMS策略中的类卸载配置。如:-XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled。


java.lang.OutOfMemoryError: unable to create new native thread

错误原因:当JVM向OS请求创建一个新线程时,而OS却由于内存不足无法创建新的native线程。 解决方法:如果JVM内存调的过大或者可利用率小于20%,可以建议将heap及perm的最大值下调,并将线程栈调小,即-Xss调小,如:-Xss128k。

java.lang.OutOfMemoryError: Requested array size exceeds VM limit

错误原因:此类信息表明应用程序(或者被应用程序调用的APIs)试图分配一个大于堆大小的数组。例如,如果应用程序new一个数组对象,大小为512M,但是最大堆大小为256M,因此OutOfMemoryError会抛出,因为数组的大小超过虚拟机的限制。 解决方法:1) 首先检查heap的-Xmx是不是设置的过小。2) 如果heap的-Xmx已经足够大,那么请检查应用程序是不是存在bug,例如:应用程序可能在计算数组的大小时,存在算法错误,导致数组的size很大,从而导致巨大的数组被分配。


java.lang.OutOfMemoryError: request

错误原因:抛出这类错误,是由于从native堆中分配内存失败,并且堆内存可能接近耗尽。这类错误可能跟应用程序没有关系,例如下面两种原因也会导致错误的发生:1) 操作系统配置了较小的交换区。2)系统的另外一个进程正在消耗所有的内存。 解决办法:1) 检查os的swap是不是没有设置或者设置的过小。2) 检查是否有其他进程在消耗大量的内存,从而导致当前的JVM内存不够分配。 注意:虽然有时


我是大脸猫
3楼 · 2021-11-09 10:36

在Java虚拟机的运行时数据区域,除了程序计数器之外,都可能会出现outOfMemoryError异常。当我们遇到实际的内存溢出异常时,首先要能根据异常异常的信息快速判断哪个区域的内存溢出,知道什么样的代码可能会导致内存溢出,以及出现异常之后该如何处理。


     操作系统为每个进程分配的内存是具有一定限制性。譬如,32位的操作系统限制为2GB。而当检查出哪个区域出现OutOfMemoryError异常时,需要平衡该进程的某个区域内存大小来解决另一个区域出现内存溢出的情况。例如,在高并发情况下,不断地创建线程可能会使线程私有的虚拟机栈出现内存溢出情况,在不能减少线程数或者更换更大位数的操作系统时,就只能通过减少最大堆和减少栈容量的方式来解决虚拟机栈导致的内存溢出。所以,无论在处理异常或者是实验,我们都要学会调整设置虚拟机启动参数(设置虚拟机启动参数(eclipse).note)。


一、Java堆溢出原因及其解决方案

     Java堆用于存储对象实例和数组的,只要不断地创建对象或者数组,并且保证GC Roots到对象之间有可达的路径来避免垃圾回收机制清除这些对象,那么在对象数量达到最大堆的容量限制后就会产生内存溢出异常。要了解Java堆内存可能溢出原因,首先我们要知道当生成新对象时,向Java堆申请内存的过程:


     ① JVM先尝试在Eden区(Young区包括1个Eden和2个survivor)分配新建对象所需要的内存;


     ② 如果内存大小足够,则申请结束。反之,执行下一步;


     ③ JVM启动新生代GC,试图将Eden区中不活跃的对象释放掉,释放后若Eden空间仍然不足以放入新对象,则试图将部分Eden中活跃对象放入Survivor区;


     ④ Survivor区被用来作为Eden及old的中间交换区域,当old区空间足够时,Survivor区的对象会被移到old区,否则会被保留在Survivor区;


     ⑤ 当old区空间不够时,JVM会在old区进行GC;


     ⑥ old区被清理后,若old区仍无法存放从Eden复制过来的部分对象,导致JVM无法在Eden区为新生对象创建内存区域,则会出现内存溢出异常(OutOfMemmory)。


例:当对象无限被创建且无法被回收时,Java堆区将会出现内存溢出异常,如下




解决方案:


     Java堆出现内存溢出异常有两种解决方案。一种是基于内存调整来改变堆区内存大小以便能够存储更多的对象,但堆内存受到物理内存的限制,当出现无法再扩展堆内存的情况时,就采用第二种方式,从代码上检查是否存在某些对象的生命周期过长、持有状态时间过长的情况,尝试减少程序在运行期的内存消耗。


二、虚拟机栈和本地方法栈溢出

     虚拟机栈和本地方法栈的内存分布在不同的虚拟机上是有差别的。例如,在HotSpot上是将虚拟机栈和本地方法栈合二为一的。虽然有-Xoss参数用于设置本地方法栈,但实际上是无效的,栈容量只由-Xss参数设定。关于虚拟机栈和本地方法栈,在Java虚拟机规范中描述了两种异常:①如果线程请求的栈深度大于虚拟机所允许的最大栈深度,将抛出StackOverflowError异常。②如果虚拟机在扩展栈时无法申请到足够的空间,则抛出OutOfMemoryError异常。


     导致这两种异常的原因是有一定区别的。虚拟机栈是线程私有的,它存储的是存放着局部变量表、操作数栈、动态链接以及方法出口等信息的栈帧,当前线程每执行一个方法时,实际上都对应着一个栈帧在虚拟机栈中出栈到入栈的过程,而当这个线程执行方法较多时,创建的栈帧也会随之越来越多,而虚拟机栈内存是受到了JVM总体内存的一个分布限制,一旦栈帧较多时,可能就会出现栈满溢出异常,也就是StackOverflowError异常。而出现OutOfMemoryError异常的情况是相对于多线程环境下而言的,一旦线程数量过多,并且都没有即使回收,从而会不断地申请内存给虚拟机栈,从而导致在扩展栈时无法申请到足够的空间,出现OutOfMemoryError异常。当然,OutOfMemoryError异常也可能出现在单钱程的情况下。当为一个线程设置虚拟机栈内存大小与其它区内存之和大于JVM所允许的最大内存,就可能出现OutOfMemoryError异常。


设置虚拟机栈内存大小为 -Xss128k,使其出现StackOverflowError异常。测试程序如下:




解决方案:


由以上可看出,StackLeak()的递归调用使该线程不断地去创建一个栈帧存入该线程的虚拟机栈中,最终导致栈满溢出出现StackOverflowError异常。解决StackOverflowError异常的方法同样有从内存大小和Java程序两个方面来解决问题。从内存大小上,增加每个线程的虚拟机栈大小,这种方式虽然简便,但在多线程的情况下,随着每个线程所属的虚拟机栈的大小的增加,所能执行的线程也就随之减少了,但我们使用这种方式来解决StackOverflowError异常时,通常要权衡这两个反比因素。从Java程序上无疑就是检查是否出现方法的不合理递归调用,从而减少栈帧的创建和及时的回收。


设置虚拟机栈大小为2M时,会出现OutOfMemoryError异常。测试程序如下:




注:Java线程执行到系统的内核线程上,有较大的风险。比如我的死机了......


解决方案:


     和StackOverflowError一样,通过改变内存大小和Java程序来解决这个问题。一般我们会优先考虑从Java程序上来进行改善,例如给线程一个固定的死亡时间,使其不会出现大量线程不能被回收的情况,或者另一方面减少线程数。而当我们无法减少线程数时和无法更换64位虚拟机的情况下,我们只能通过减少堆内存和栈容量来权衡这一部分的内存消耗。


三、方法区和运行时常量池溢出

     在jdk1.7之前,运行时常量池属于方法区的一部分,也就是运行时常量池处于永久代。而方法区出现的内存溢出异常经常是由运行时常量池内存溢出异常所导致的。但在jdk1.7时,为了消除永久代,常量池位置发生了一定的变化,由原来的方法区转移到了堆区,但永久代实际上还是存在的,因为内加载机制还在永久代。我们也知道,堆区是GC管理的主要区域,而常量池存储了该类的所有常量,如果使其处于永久代不进行及时的回收,导致内存溢出可能会是经常的。放入堆区个人理解就是为了方便及时合理回收运行时常量池。在jdk1.8之后,完全消除永久代这一个区域,jvm将方法区放入了一个与堆不相连的区域,该区域称为元空间(元空间介绍引用:https://www.cnblogs.com/duanxz/p/3520829.html)。


     以此代码为例,通过设置不同的虚拟机启动参数来探究内存溢出异常:




jdk1.6处于永久代的常量池所出现的OutOfMemoryError异常。我们设置永久代内存大小为10M(-XX:PermSize=10M -XX:MaxPermSize=10M)测试结果如下:




由以上可以看出,在jdk1.6之前运行时常量池出现内存溢出异常在PermGen space,也就是方法区。


jdk1.7运行时常量池放入堆区。我们设置堆内存大小为20M(-Xmx20M -Xms20M),测试结果如下:




说明jdk1.7中,运行时常量池在堆区。


四、本机直接内存溢出

     本机直接内存容量可以通过-XX:MaxDirectMemorySize指定,如果不指定,则默认与Java堆最大值(-Xmx制定)一样。NIO提供了一个不经过JVM直接访问本机物理内存的类——DirectMemory。DircetMemory继承与ByteBuffer,但和普通的ByteBuffer不同,普通的ByteBuffer在堆上进行内存分配,其最大的内存受到堆内存的限制。而DircetMemory是向本机物理内存申请内存分配,所以其大小只受物理内存的限制。由于直接内存是介于堆区和操作系统之间的一个Buffer,所以读写操作比普通的Buffer要快,同样也造成创建和销毁较普通Buffer慢的特点。以下代码清单中直接越过DircetByteBuffer(存储在Java堆上的操作直接内存的一个对象引用)类,直接通过反射获取unsafe实例进行内存分配。因为虽然使用了DircetByteBuffer分配内存也会抛出内存溢出异常,但它抛出异常时并没有真正向操作系统申请分配内存,而是通过计算得知内存无法分配,于是手动抛出异常,真正申请分配内存的方法是unsafe.allocateMemory()。




回答: 2021-11-11 09:13

211

123654
4楼 · 2021-11-12 16:57

OutOf MemoryError这种错误可以细分为多种不同的错误,每种错误都有自身的原因和解决办法,如下所示:

java.lang.OutOfMemoryError: Java heap space

错误原因:此OOM是由于JVM中heap的最大值不满足需要。

解决方法:1) 调高heap的最大值,即-Xmx的值调大。2) 如果你的程序存在内存泄漏,一味的增加heap空间也只是推迟该错误出现的时间而已,所以要检查程序是否存在内存泄漏。

java.lang.OutOfMemoryError: GC overhead limit exceeded

错误原因:此OOM是由于JVM在GC时,对象过多,导致内存溢出,建议调整GC的策略,在一定比例下开始GC而不要使用默认的策略,或者将新代和老代设置合适的大小,需要进行微调存活率。

解决方法:改变GC策略,在老代80%时就是开始GC,并且将-XX:SurvivorRatio(-XX:SurvivorRatio=8)和-XX:NewRatio(-XX:NewRatio=4)设置的更合理。

java.lang.OutOfMemoryError: Java perm space

错误原因:此OOM是由于JVM中perm的最大值不满足需要。

解决方法:调高heap的最大值,即-XX:MaxPermSize的值调大。

另外,注意一点,Perm一般是在JVM启动时加载类进来,如果是JVM运行较长一段时间而不是刚启动后溢出的话,很有可能是由于运行时有类被动态加载进来,此时建议用CMS策略中的类卸载配置。如:-XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled。

java.lang.OutOfMemoryError: unable to create new native thread

错误原因:当JVM向OS请求创建一个新线程时,而OS却由于内存不足无法创建新的native线程。

解决方法:如果JVM内存调的过大或者可利用率小于20%,可以建议将heap及perm的最大值下调,并将线程栈调小,即-Xss调小,如:-Xss128k。

java.lang.OutOfMemoryError: Requested array size exceeds VM limit

错误原因:此类信息表明应用程序(或者被应用程序调用的APIs)试图分配一个大于堆大小的数组。例如,如果应用程序new一个数组对象,大小为512M,但是最大堆大小为256M,因此OutOfMemoryError会抛出,因为数组的大小超过虚拟机的限制。

解决方法:1) 首先检查heap的-Xmx是不是设置的过小。2) 如果heap的-Xmx已经足够大,那么请检查应用程序是不是存在bug,例如:应用程序可能在计算数组的大小时,存在算法错误,导致数组的size很大,从而导致巨大的数组被分配。

java.lang.OutOfMemoryError: request < size> bytes for < reason>. Out of swap space

错误原因:抛出这类错误,是由于从native堆中分配内存失败,并且堆内存可能接近耗尽。这类错误可能跟应用程序没有关系,例如下面两种原因也会导致错误的发生:1) 操作系统配置了较小的交换区。2)系统的另外一个进程正在消耗所有的内存。

解决办法:1) 检查os的swap是不是没有设置或者设置的过小。2) 检查是否有其他进程在消耗大量的内存,从而导致当前的JVM内存不够分配。

注意:虽然有时< reason>部分显示导致OOM的原因,但大多数情况下,< reason>显示的是提示分配失败的源模块的名称,所以有必要查看日志文件,如crash时的hs文件。


帅帅马
5楼 · 2021-11-14 10:45

OutOf MemoryError这种错误可以细分为多种不同的错误,每种错误都有自身的原因和解决办法,如下所示:

java.lang.OutOfMemoryError: Java heap space

错误原因:此OOM是由于JVM中heap的最大值不满足需要。

解决方法:1) 调高heap的最大值,即-Xmx的值调大。2) 如果你的程序存在内存泄漏,一味的增加heap空间也只是推迟该错误出现的时间而已,所以要检查程序是否存在内存泄漏。

java.lang.OutOfMemoryError: GC overhead limit exceeded

错误原因:此OOM是由于JVM在GC时,对象过多,导致内存溢出,建议调整GC的策略,在一定比例下开始GC而不要使用默认的策略,或者将新代和老代设置合适的大小,需要进行微调存活率。

解决方法:改变GC策略,在老代80%时就是开始GC,并且将-XX:SurvivorRatio(-XX:SurvivorRatio=8)和-XX:NewRatio(-XX:NewRatio=4)设置的更合理。

java.lang.OutOfMemoryError: Java perm space

错误原因:此OOM是由于JVM中perm的最大值不满足需要。

解决方法:调高heap的最大值,即-XX:MaxPermSize的值调大。

另外,注意一点,Perm一般是在JVM启动时加载类进来,如果是JVM运行较长一段时间而不是刚启动后溢出的话,很有可能是由于运行时有类被动态加载进来,此时建议用CMS策略中的类卸载配置。如:-XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled。

java.lang.OutOfMemoryError: unable to create new native thread

错误原因:当JVM向OS请求创建一个新线程时,而OS却由于内存不足无法创建新的native线程。

解决方法:如果JVM内存调的过大或者可利用率小于20%,可以建议将heap及perm的最大值下调,并将线程栈调小,即-Xss调小,如:-Xss128k。

java.lang.OutOfMemoryError: Requested array size exceeds VM limit

错误原因:此类信息表明应用程序(或者被应用程序调用的APIs)试图分配一个大于堆大小的数组。例如,如果应用程序new一个数组对象,大小为512M,但是最大堆大小为256M,因此OutOfMemoryError会抛出,因为数组的大小超过虚拟机的限制。

解决方法:1) 首先检查heap的-Xmx是不是设置的过小。2) 如果heap的-Xmx已经足够大,那么请检查应用程序是不是存在bug,例如:应用程序可能在计算数组的大小时,存在算法错误,导致数组的size很大,从而导致巨大的数组被分配。

java.lang.OutOfMemoryError: request < size> bytes for < reason>. Out of swap space

错误原因:抛出这类错误,是由于从native堆中分配内存失败,并且堆内存可能接近耗尽。这类错误可能跟应用程序没有关系,例如下面两种原因也会导致错误的发生:1) 操作系统配置了较小的交换区。2)系统的另外一个进程正在消耗所有的内存。

解决办法:1) 检查os的swap是不是没有设置或者设置的过小。2) 检查是否有其他进程在消耗大量的内存,从而导致当前的JVM内存不够分配。

注意:虽然有时< reason>部分显示导致OOM的原因,但大多数情况下,< reason>显示的是提示分配失败的源模块的名称,所以有必要查看日志文件,如crash时的hs文件。


visonx
6楼 · 2021-12-17 10:32

错误原因:此OOM是由于JVM中perm的最大值不满足需要。 解决方法:调高heap的最大值,即-XX:MaxPermSize的值调大。 另外,注意一点,Perm一般是在JVM启动时加载类进来,如果是JVM运行较长一段时间而不是刚启动后溢出的话,很有可能是由于运行时有类被动态加载进来,此时建议用CMS策略中的类卸载配置。如:-XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled。




相关问题推荐

  • 回答 19

    1)作用不同: throw用于程序员自行产生并抛出异常; throws用于声明在该方法内抛出了异常2) 使用的位置不同: throw位于方法体内部,可以作为单独语句使用; throws必须跟在方法参数列表的后面,不能单独使用。3)内容不同: throw抛出一个异常对象,且只能是...

  • 回答 11

    基本执行过程如下:1)程序首先执行可能发生异常的try语句块。2)如果try语句没有出现异常则执行完后跳至finally语句块执行;3)如果try语句出现异常,则中断执行并根据发生的异常类型跳至相应的catch语句块执行处理。4)catch语句块可以有多个,分别捕获不同类型...

  • 回答 16

    异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误,只要程序设计得没有问题通常就不会发生。受检异常跟程序运行的上下文环境有关,即使程序设计无误,仍然可能因使用的问题而引发。Java编译...

  • 回答 10

    Java 通过面向对象的方法进行异常处理,把各种不同的异常进行分类,并提供了良好的接口。在Java 中,每个异常都是一个对象,它是Throwable 类或其子类的实例。当一个方法出现异常后便抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获...

  • 回答 6

    问题的根本原因是工程中某个jar包的版本(jar包编译时的所用的jdk版本)高于工程build path中jdk的版本,这个是不兼容的! 编程中遇到此异常Unsupported major.minor version 52.0(根据版本号,这里可以为其他数值,52是1.8jdk jar包与 1.8以下低版本jdk不匹配)...

没有解决我的问题,去提问