JAVA---为什么递归太深会导致栈溢出

一、思考

对于这个问题,我是这样想的:

我们知道栈大小是可以通过参数(-Xss)设置的,栈是线程私有的,线程内部的每个方法调用会创建一个栈帧,所以如果“栈帧的数量*每个栈帧的大小>栈大小”时便会发生“栈溢出”。

这样的设想基于:

  1. -Xss设置的参数是针对每一个栈的,而非JVM所有线程栈内存总大小。
  2. 每个方法的调用将创建一个栈帧。
  3. SUM(每个栈帧大小)>栈大小发生栈溢出。

接下来我们将分析一下我们的设想是否正确。

二、分析

2.1第一点:-Xss设置的参数是针对每一个栈的,而非JVM所有线程栈内存总大小

验证程序

//JVM参数:-Xss108k

//对比“一个线程栈溢出”和“两个线程栈溢出”时的栈深度,从而验证设置的栈大小是否只是限制一个线程栈 
//大小

private static int c1 = 0;
private static int c2 = 0;
public static void main(String[] args) {
    //线程一递归
	new Thread(() -> {
		try {
			m1();
		} catch (StackOverflowError error) {
			System.out.println("栈溢出 c1=" + c1);
		}
	}).start();
    //线程二递归
	new Thread(() -> {
		try {
			m2();
		} catch (StackOverflowError error) {
			System.out.println("栈溢出 c2=" + c2);
		}
	}).start();

}

public static void m1() {
	c1++;
	m1();
}

public static void m2() {
	c2++;
	m2();
}

执行结果

两个线程:

一个现场:

总结

从执行结果来看,并没有因为线程个数增加,导致单个线程栈深度降低,所以可以验证第一点正确。

2.2第二点:每个方法的调用将创建一个栈帧

在《深入了解JVM》一书中,介绍到:每一个方法调用时,都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口灯信息。所以这第二点也可验证。

2.3第三点:SUM(每个栈帧大小)>栈大小发生栈溢出

我们知道栈是一种后进先出的数据结构,递归太深,意味着只有栈帧入栈还没等到栈帧出栈,就已经超过栈所能承受的大小。所以这一点也不难理解。

2.4总结

在《深入了解JVM》一书中说到:当线程请求的栈深度超过虚拟机允许的栈深度时,便会抛出StackOverFlowError。这是一个抽象化的总结,我们对此做了如上分析。

三、其他

3.1栈溢出时如何解决

线上临时解决办法

重新调整JVM参数-Xss,重启应用

代码层面

将递归改为循环,如上问中的代码可修改为:

new Thread(() -> {
            try {
                //这里只是模仿死循环。真实业务肯定有循环终止条件的
                while(true){
                  m1();
                }
            } catch (StackOverflowError error) {
                System.out.println("栈溢出 c1=" + c1);
            }
}).start();

//去掉递归
public static void m1() {
        c1++;
}

3.2栈大小设置多少合适

在上文线上临时解决方案中,我们通过增大栈大小,使程序继续运行。那为什么我们一开始发布程序的时候就将“栈大小”设置的大一些呢?

首先,操作系统分配给每个进程的内存是有限制的。那么:

可用的栈内存=进程最大内存-堆内存-方法区内存-程序计数器内存-虚拟机本身耗费内存

而栈是线程私有的,那么可以认为:

程序可建立的线程数量=可用栈内存/栈大小

这样当栈大小设置太大时,就会导致创建的线程数量太少。这样在多线程的情况下便可能发生“内存溢出”情况。

在x64位Linux操作系统上,JVM默认的栈大小为1024kb。

由于我们线上的程序要支持高并发场景,所以栈的大小设置为256kb,这里仅供参考。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值