深入理解java.lang.StackOverflowError和java.lang.OutOfMemoryError错误

名词解释

StackOverflowError:栈溢出错误

OutOfMemoryError:内存不足错误

栈溢出
如果一个线程在计算时所需要用到栈大小 > 配置允许最大的栈大小,那么Java虚拟机将抛出StackOverflowError

内存溢出
如果一个线程可以动态地扩展本机方法栈,并且尝试本地方法栈扩展(没有大于配置允许最大的栈大小),但是内存不足可以提供, 或者如果不能提供足够的内存来为新线程创建初始的堆(如new Object),那么Java虚拟机将抛出OutOfMemoryError。
 

什么是栈?

我这里讲得比简单,建议大家看看java虚拟机模型。下图是一个线程里面的存放的数据
栈结构

栈的特点

也叫栈内存,是java虚拟机的内存模型之一,每当启动一个新线程时,Java虚拟机都会为它分配一个Java栈。虚拟机只会直接对Java栈执行两种操作,以帧为单位的压栈和出栈。

栈存储的是什么
方法内的局部变量表、操作数栈、动态链接、方法出口信息、其他等信息
栈的生命周期
是在线程创建时创建,线程结束而消亡,释放内存,由此可见栈内存是私有的。
栈内存是以栈帧(Stack Frame)为单位存储,栈帧是一个内存区块,是一个有关方法(Method)和运行期数据的数据集。
当一个方法M1被调用时就产生了一个栈帧S1,并被压入到栈中,M1方法又调用了M2方法,于是产生栈帧S2也被压入栈,M2方法执行完毕后,S2栈帧先出栈,S1栈帧再出栈,遵循“先进后出”原则。
 

栈帧

这里写图片描述

栈帧存储数据包含以下5个部分

1.局部变量表
保存函数的参数以及局部变量用的,局部变量表中的变量只在当前函数调用中有效,当函数调用结束后,随着函数栈帧的销毁,局部变量表也会随之销毁。

存放基本数据类型变量(boolean、byte、char、short、int、float)、引用类型的变量(reference)、returnAddress(指向一条字节码指令的地址)类型的变量。
2.操作数栈
主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。只支持出栈入栈操作。在概念模型中,两个栈帧是相互独立的。但是大多数虚拟机的实现都会进行优化,令两个栈帧出现一部分重叠。令下面的部分操作数栈与上面的局部变量表重叠在一块,这样在方法调用的时候可以共用一部分数据,无需进行额外的参数复制传递。

3.动态链接
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。在Class文件的常量池中存有大量的 符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用一部分会在类加载阶段或第一次使用的时候转化为直接引用,这种转化 称为静态解析。另外一部分将在每一次的运行期期间转化为直接引用,这部分称为动态连接。
4.方法出口信息
在方法退出之前,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上 层方法的执行状态。一般来说,方法正常退出时,调用者PC计数器的值就可以作为返回地址,栈帧中很可能会保存这个计数器值。而方法异常退出时,返回地址是 要通过异常处理器来确定的,栈帧中一般不会保存这部分信息
5.其他

模拟StackOverflowError堆栈溢出错误


public class TestDemo {

    private int index = 1;

    public void method() {
        index++;
        method();
    }

    @Test
    public void testStackOverflowError() {
        try {
            method();
        } catch (StackOverflowError e) {
            System.out.println("程序所需要的栈大小 > 允许最大的栈大小,执行深度: " + index);
            e.printStackTrace();
        }
    }
}

程序所需要的栈大小 > 允许最大的栈大小,执行深度: 24612

java.lang.StackOverflowError
	at com.collection.map.MapDemo.method(MapDemo.java:29)
	at com.collection.map.MapDemo.method(MapDemo.java:29)
	at com.collection.map.MapDemo.method(MapDemo.java:29)
	at com.collection.map.MapDemo.method(MapDemo.java:29)
	at com.collection.map.MapDemo.method(MapDemo.java:29)

程序详解:

1.执行main函数会创建一个线程,同时创建一个虚拟机栈(栈内存)

2.调用statck.method()时,会对method()进行压栈操作,将method()运行期数据的数据集(批注1)保存到栈帧1(Stack Frame)(如果main方法里面调用多个方法,会执行多个压栈操作)。

3.method()递归调用时,都会产生一个新的栈帧区块,这时就会连续的产生新的栈帧区块

4.当栈内存超过系统配置的栈内存-Xss:2048(批注2),就会出现java.lang.StackOverflowError异常。这也是为什么对于需要谨慎使用递归调用的原因!
 

批注1:基本数据类型变量、引用类型的变量、returnAddress类型的变量、操作数栈、动态链接、方法出口等
批注2:-Xss 为jvm启动的每个线程分配的内存大小
 

模拟OutOfMemoryError堆栈溢出错误

public class Heap {

    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        while (true) {
            list.add(new Heap());
        }
    }
}
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:3210)
	at java.util.Arrays.copyOf(Arrays.java:3181)
	at java.util.ArrayList.grow(ArrayList.java:261)
	at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
	at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
	at java.util.ArrayList.add(ArrayList.java:458)

List是动态增长的,因此容量不够了,就会扩容,一旦空闲内存分配完毕,请求不到其他内存,就抛出OutOfMemoryError。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值