OOM vs StackOverflow

Published: 19 Dec 2018 Category: java

一、StackOverflow

每当java程序启动一个新的线程时,java虚拟机会为他分配一个栈,java栈以帧为单位保持线程运行状态;当线程调用一个方法是,jvm压入一个新的栈帧到这个线程的栈中,只要这个方法还没返回,这个栈帧就存在。

如果方法的嵌套调用层次太多(如递归调用), 随着java栈中的帧的增多,最终导致这个线程的栈中的所有栈帧的大小的总和大于-Xss设置的值,而产生StackOverflowError溢出异常。

复现:函数递归调用自己。启动时把-Xss(设置每个线程的堆栈大小)设置得小一点。

注意:如果是新建线程,报出的错会是OOM!

注意:如果你把虚拟机参数Xss调大了,每个线程的占用的栈空间也就变大了,那么可以建立的线程数量必然减少,有可能诱发OOM。(当前大部分的Java 虚拟机都可动态扩展)

二、OutOfMemory

这种错误发生的情况就比较多。(程序计数器内存区域是唯一一个在Java 虚拟机规范中没有规定任何OutOfMemoryError 情况的区域。)

2.1 栈内存溢出

java程序启动一个新线程时,没有足够的空间为线程分配java栈,JVM则抛出OutOfMemoryError异常。

2.2 堆内存溢出

java堆用于存放对象的实例,当需要为对象的实例分配内存时,而堆的占用已经达到了设置的最大值(通过-Xmx 设置JVM最大可用内存)设置最大值,则抛出OutOfMemoryError异常。

2.3 方法区内存溢出

方法区(也是所有线程共享)用于存放java类的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。在类加载器加载class文件到内存中的时候,JVM会提取其中的类信息,并将这些类信息放到方法区中。

当需要存储这些类信息,而方法区的内存占用又已经达到最大值(通过-XX:MaxPermSize);将会抛出OutOfMemoryError异常对于这种情况的测试,基本的思路是运行时产生大量的类去填满方法区,直到溢出。

可以借助CGLib直接操作字节码运行,生成大量的动态类。

三、例子

3.1 栈溢出

例如,通过递归调用方法,不停的产生栈帧,一直把栈空间堆满,直到抛出异常:

package com.demo3;

public class OOMTest {
    public void stackOverFlowMethod() {
        stackOverFlowMethod();
    }     

    public static void main(String... args) {
        OOMTest oom = new OOMTest();
        oom.stackOverFlowMethod();
    }
}

结果:

Exception in thread "main" java.lang.StackOverflowError
at com.demo3.OOMTest.stackOverFlowMethod(OOMTest.java:5)
at com.demo3.OOMTest.stackOverFlowMethod(OOMTest.java:5)
at com.demo3.OOMTest.stackOverFlowMethod(OOMTest.java:5)
at com.demo3.OOMTest.stackOverFlowMethod(OOMTest.java:5)
at com.demo3.OOMTest.stackOverFlowMethod(OOMTest.java:5)
at com.demo3.OOMTest.stackOverFlowMethod(OOMTest.java:5)
at com.demo3.OOMTest.stackOverFlowMethod(OOMTest.java:5)
at com.demo3.OOMTest.stackOverFlowMethod(OOMTest.java:5)
.....

3.2 内存溢出示例

内存溢出是指当我们新建一个实例对象时,实例对象所需占用的内存空间大于堆的可用空间。 如果出现了内存溢出问题,这往往是程序本生需要的内存大于了我们给虚拟机配置的内存,这种情况下,我们可以采用调大-Xmx来解决这种问题。

package com.demo3;

import java.util.ArrayList;
import java.util.List;

public class OOMTest {

    public static void main(String[] args) {
        List<byte[]> buffer = new ArrayList<byte[]>();
        buffer.add(new byte[10 * 1024 * 1024]);
    }

}

通过如下命令运行程序:

java -verbose:gc -Xmn10M -Xms20M -Xmx20M -XX:+PrintGC OOMTest

Xmn设置年轻代大小为10M, Xms设置初始内存为20M,Xmx整个JVM内存上限20M(整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小)

输出结果:

[GC 836K->568K(19456K), 0.0234380 secs]
[GC 568K->536K(19456K), 0.0009309 secs]
[Full GC 536K->463K(19456K), 0.0085383 secs]
[GC 463K->463K(19456K), 0.0003160 secs]
[Full GC 463K->452K(19456K), 0.0062013 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at com.demo3.OOMTest.main(OOMTest.java:10)

四、联系和区别

如果在一个线程计算过程中不允许有更大的本地方法栈,那么JVM就抛出StackOverflowError。

如果本地方法栈可以动态地扩展,并且本地方法栈尝试过扩展了,但是没有足够的内容分配给它,再或者没有足够的内存为线程建立初始化本地方法栈,那么JVM抛出的就是OutOfMemoryError。

REF

https://blog.csdn.net/weixin_40667145/article/details/78556182