OutOfMemoryError的常⻅原因

165 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第16天,点击查看活动详情

OOM: 当JVM内存严重不足时,就会抛出 java.lang.OutOfMemoryError 错误。除了程序计数器外,虚拟机内存的其它几个运行时区域都有发生 OutOfMemoryError 异常的可能。

如果系统定义了太多的类,导致⽅法区溢出,虚拟机同样会抛出内存溢出错误:OutOfMemoryError:PermGen space或者 OutOfMemoryError:Metaspace;假如java堆中没有完成实例分配,并且堆也不能再扩展时,java虚拟机就会抛出OOM。

一、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堆区将会出现内存溢出异常。

解决方案: 1、调整内存。基于内存调整来改变堆区内存大小以便能够存储更多的对象,但堆内存受到物理内存的限制,当出现无法再扩展堆内存的情况时,就采用第二种方式。

2、检查代码。从代码上检查是否存在某些对象的生命周期过长、持有状态时间过长的情况,尝试减少程序在运行期的内存消耗。

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

在 Java 虚拟机规范中描述了两种异常:

  1. 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出 StackOverflowError 异常。
  2. 如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出 OutOfMemoryError 异常。

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

方法区用于存放 Class 的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。当前的很多主流框架,如 Spring、Hibernate,在对类进行增强时,会使用到 CGLib 这类字节码技术,增强的类越多,就需要越大的方法区来保证动态生成的 Class 可以加载入内存,这样也可能会出现 OOM 异常。

四、本机直接内存溢出

本机直接内存容量可以通过-XX:MaxDirectMemorySize指定,如果不指定,则默认与Java堆最大值(-Xmx制定)一样。NIO提供了一个不经过JVM直接访问本机物理内存的类——DirectMemory。DircetMemory继承与ByteBuffer,但和普通的ByteBuffer不同,普通的ByteBuffer在堆上进行内存分配,其最大的内存受到堆内存的限制。而DircetMemory是向本机物理内存申请内存分配,所以其大小只受物理内存的限制。由于直接内存是介于堆区和操作系统之间的一个Buffer,所以读写操作比普通的Buffer要快,同样也造成创建和销毁较普通Buffer慢的特点。

image.png