Java的内存溢出(OOM)

时间:2021-10-04 16:56:15

JAVA内存区域中不同的结构会由于不同的原因而导致内存溢出。JAVA内存主要分为堆,栈,方法区和程序计数器四个部分。程序计数器是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。其他三个区域都有可能发生内存溢出。下面我们来具体说说。

对于内存溢出(Memory Overflow),还有一个相似的概念就是内存泄露(Memory Leak)。它们有着本质的不同,内存泄露会导致内存溢出。内存泄露是没有必要存活的对象应该被GC回收,然而还有引用指向对象导致GC不能回收,这一般是程序的问题。

一 堆溢出

      堆用来存储对象实例,只要不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,则在对象数量到达最大堆的容量限制后会产生内存溢出异常。内存溢出在堆中就会导致OOM。下面的例子在需要设定虚拟机的参数:-Xmx(堆的最大值)和 -Xms(堆的初始值)。下图显示异常位置:Java heap space

Java的内存溢出(OOM)

二 栈溢出

 栈溢出有两种异常:

1.如果线程请求的栈深度大于虚拟机所允许的最大深度,则将抛出*Error异常。

2.如果虚拟机在扩展栈时无法申请到做够的内存空间,就会抛出OutOfMemoryError异常。

上面两种情况存在着相互重叠,本质是对同一件事情的两种描述而已。可以创建很多个栈帧来让它溢出,其实就是定义一个递归方法,让栈帧占满栈空间,这会导致*Error异常,栈深度一般在1000到2000。

创建很多个线程可以使虚拟机抛出OutOfMemoryError,由于Java线程是映射到操作系统的内核上,因此在做这个实验时很容易导致死机,我试验过一次,电脑就卡死了。下面就展示抛出的第一个异常:

Java的内存溢出(OOM)


三 方法区溢出
在JDK1.6之前,常量池是在方法区中的,则可以通过不断的创建常量使得方法区(永久代/持久代)溢出。不过在Java7,常量池被移到了堆中,这时在JRE7中就不会因为常量池而导致方法取溢出。同时方法区又是存放Class的相关信息,动态生成很多类会也导致方法区溢出。
在Java6及之前的版本,可以通过参数-XX:PermSize=10M -XX:MaxPermSize=10M 限制方法区的大小,从而间接限制常量池的大小。例子如下图:
Java的内存溢出(OOM)

四 本机直接内存溢出
直接内存通过在系统中直接分配内存得到,使用NIO包可以获得直接内存。下面的例子产生了OOM错误,可以发现抛出的异常没有具体指向哪个空间:
Java的内存溢出(OOM)


参考:《深入理解Java虚拟机》