OOM,全称"Out Of Memory",内存耗尽。当JVM因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时,就会抛出这个error。

内存泄露:申请试用完的内存没有释放,导致虚拟机不能再次试用该内存,此时这段内存就泄露了,因为申请者不用了,而不能被虚拟机分配给别人用。

内存溢出:申请的内存超出了JVM能提供的内存大小,此时称之为溢出。

导致OOM的原因:

  1. 分配的少了:比如虚拟机本身可使用的内存(一般通过启动时的VM参数指定)太少了。
  2. 应用用的太多了,并且用完没有释放,浪费了。此时就会造成内存泄露或者内存溢出。

常见的java OOM

1,java.lang.OutOfMemoryError:Java heap space

即使有足够的物理内存可用,只要达到堆空间设置的大小限制,此异常仍然被触发。

触发该错误最常见的原因就是应用程序需要的堆空间大于JVM所能提供的空间大小。解决方法是提供更大的堆空间。

也有更复杂的原因导致:

  • 流量/数据量峰值:应用程序在设计之初均有用户量和数据量的限制,某一时刻,当用户数量或数据量突然达到一个峰值,并且这个峰值已经超过了设计之初预期的阀值,那么以前正常的功能将会停止,并触发该错误。
  • 内存泄露:特定的变成错误会导致应用程序不停的消耗更多的内存,每次使用有内存泄露风险的功能更就会留下一些不能被回收的对象到堆空间,随着时间的推移,泄露的对象会消耗所有的堆空间,最终触发该错误。

1)简单示例

创建2*1024*1024个元素的整型数组,当你尝试编译并指定12m堆空间运行时(java -Xmx12m OOM)将会抛出java.lang.OutOfMemoryError:Java heap space错误,而指定13m堆空间时,将正常的运行。

 运行如下:

 2)内存泄露示例

 由于key实体没有实现equals()方法,导致for循环中每次执行m.containsKey(new Key(i))结果均为false,其结果就是HashMap中的元素将一直增加。随着时间的推移,越来越多的key对象进入堆空间且不能被垃圾收集器回收(m为局部变量,GC会认为这些对象一直可用,所以不会回收),直到所有的堆空间被占用,最后抛出该错误。

2,java.lang.OutOfMemoryError:GC overhead limit exceeded

java运行时环境(JRE)包含一个内置的垃圾回收进程,清空空闲的内存。

默认情况下,当应用程序花费98%的时间用来GC并且回收了不到2%的堆内存,就会抛出java.lang.OutOfMemoryError:GC overhead limit exceeded错误。具体的表示就是你的应用几乎耗尽所有可用的内存,并且GC多次均未能清理干净。

示例:

初始化一个map并在无限循环中不停的添加键值对,运行后将会抛出GC overhead limit exceeded错误。

 3,java.lang.OutOfMemoryError:Permgen space

java中堆空间是JVM管理的最大一块内存空间,可以在JVM启动时指定堆空间的大小,其中堆被划分成不同的区域:新生代(Young)和老年代(Old),新生代又被划分为3个区域:Eden、From Survivor、To Survivor。如下图所示:

 持久代主要存储的是每个类的信息,如:类加载器引用,运行时常量池(所有常量、字段引用、方法引用、属性)、字段(Field)数据、方法(Method)数据、方法代码、方法字节码等等。PermGen的大小取决于被加载的数量以及类的大小。

出现java.lang.OutOfMemoryError:PermGen space错误的原因是:太多的类或者太大的类被加载到permanent generation(持久代)。

示例:

 运行时请设置JVM参数:-XX:MaxPermSize=5m,值越小越好。需要注意的是JDK8已经完全移除持久代空间,取而代之的是元空间(Metaspace),所以示例最好在JDK1.7或者JDK1.6下运行。代码在运行时不停的生成类并加载到持久代中,直到撑满持久代内存空间,最后抛出java.lang.OutOfMemoryError:PermGen space错误。

4,java.lang.OutOfMemoryError:Metaspace

前面提过,PermGen区域用于存储类的名称和字段,类的方法,方法的字节码,常量池,JIT优化等。但从Java8开始,Java中的内存模型发生了重大变化:引入了称为Metaspace的新内存区域,而删除了PermGen区域。注意:不是简单的将PermGen区所存储的内容直接移动Metaspace区,PermGen区中的某些部分,已经移动到了普通堆里面。

 元空间大小的要求取决于加载的类的数量以及这种类声明的大小。出现该错误的主要原因:太多的类或太大的类加载到元空间。

示例:

默认情况下,对于64位服务器端JVM,Metaspace默认大小是21M(初始限制值)。

对于上述问题,一种方法增加元空间的大小-XX:MaxMetaspaceSize=512m;另外一个方法就是删除此参数来完全解除对Metaspace大小的限制。

5,java.lang.OutOfMemoryError:Unable to create new native thread

JVM中的线程完成自己的工作也是需要一些空间的,当有足够多的线程却没有那么多的空间。如下图所示:

 出现这个错误就意味着java应用程序已达到其可以启动线程数量的极限了。

原因分析:

当JVM向OS请求创建一个线程时,而OS却无法创建新的native线程时就会抛出unable to create new native thread错误。一台服务器可以创建的线程数依赖于物理配置和平台。

示例:

 有时候可以通过在OS级别增加线程限制来绕过这个错误。如果你限制了JVM可在用户空间创建的线程数,那么你可以检查并增加这个限制。

linux查看用户线程数量

ulimit -u

查看配置用户线程的文件

vim /etc/security/limits.d/90-nproc.conf

6,java.lang.OutOfMemoryError:Out of swap space?

java应用程序在启动时会指定所需要的内存大小,可以通过-Xmx和其他类似的启动参数来指定。在JVM请求的总内存大于物理内存的情况下,操作系统会将内存中的数据交换到磁盘上去。

 Out of swap space?表示交换空间也将耗尽,并且由于缺少物理内存和交换空间,再次尝试分配内存也将失败。

当应用程序向JVM native heap请求分配内存失败并且native heap也将耗尽时,JVM会抛出out of swap space错误。该错误消息中包含分配失败的大小(以字节为单位)和请求失败的原因。

该错误往往是由操作系统级别的问题引起的。

  • 操作系统配置的交换空间不足
  • 系统上的另一个进程消耗所有内存资源

解决方法:通常最简单的方法就是增加交换空间,不同平台实现的方式有所不同。

linux系统

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

java对应用程序可以分配的最大数组大小有限制。不同平台限制有所不同,但通常在1到21亿个元素之间。

 当遇到这种错误时,意味着应用程序试图分配大于java虚拟机可以支持的数组。

示例

for (int i = 3; i >= 0; i--) {
    try {
        int[] arr = new int[Integer.MAX_VALUE-i];
        System.out.format("Successfully initialized an array with %,d elements.\n", Integer.MAX_VALUE-i);
    } catch (Throwable t) {
        t.printStackTrace();
    }
}

解决方法根据实际情况减少数组的大小。

8,Out of memory:Kill process or sacrifice child

当内核检测到系统内存不足时,OOM killer(内存杀手)被激活,然后选择一个进程杀掉。选择的算法和想法都很朴实:谁占有内存最多,谁就被干掉。

 当可用虚拟内存(包括交换空间)消耗到让整个操作系统面临风险时,就会产生Out of memory: kill process or sacrifice child错误。在这种情况下,OOM Killer会选择"流氓进程"并杀死它。

示例:

 解决这个问题最有效也是最直接的方法就是升级内存。

 

 posted on 2020-12-16 19:06  会飞的金鱼  阅读(1135)  评论(0)    收藏  举报