为什么它不会因stackoverflow错误而终止

时间:2020-03-28 15:40:11

标签: java recursion try-catch puzzle

我从一本名为Java Puzzlers的书中碰到了该程序,书中解释了它的行为,但我无法掌握所有内容,书中说以下程序终止于 1.7×10 291 年,假设正在处理它的计算机,它还说它抛出StackOverFlowError 2 1,024 ,但仍在运行。

public class Workout {

    public static void main(String[] args) {
        workHard();
        System.out.println("It’s nap time.");
    }

    private static void workHard() {
        try {
            workHard();
        } finally {
            workHard();
        }
    }
}

我想了解多余的堆栈在哪里,并且该程序会影响正在消耗其堆栈的计算机上运行的其他程序。 这本书中的技术性说明可以有人外行术语来解释,就像非常强力一样。

3 个答案:

答案 0 :(得分:1)

让我们将堆栈的底部索引为0,然后在堆栈级别1,2,3处进行后续调用(因为它是递归的),依此类推。每次调用workHard都会使堆栈索引增加1,而每次返回都应使堆栈长度减少1。

由于没有return语句,所以这段代码形成了无限递归,而退出此递归循环的唯一方法是中断该循环的堆栈溢出异常。

考虑修改后的代码段,而不需要尝试/最后尝试:

public class Workout {

    public static void main(String[] args) {
        workHard();
        System.out.println("It’s nap time.");
    }

    private static void workHard() {
        workHard();
    }
}

在上面的代码中,假设您在堆栈的第n个索引处获得了异常。异常被抛出到第n-1个堆栈,而该堆栈没有任何处理异常的方法,因此该异常被抛出到第n-2个堆栈级别和第0个级别。在第0级获得异常后,将异常抛出给main方法,该方法也没有任何机制可以处理此异常,并且会抛出异常。

现在使用try块进入代码:

public class Workout {

    public static void main(String[] args) {
        workHard();
        System.out.println("It’s nap time.");
    }

    private static void workHard() {
        try {
            workHard();
        } finally {
            workHard();
        }
    }
}

同样,在开始时,将首先从try块进行递归调用,并继续进行调用,直到第n个级别,在此得到第一个堆栈溢出异常,同时递归调用第n + 1个堆栈级别。 >

对于来自try块的每个堆栈溢出异常,下一行执行将是对finally块中workHard的调用。在第n级的堆栈溢出异常的高峰时,将捕获此异常,并且执行将最终转到第n级的块。由于我们的堆栈大小已经非常有限,因此,此finally块还将引发堆栈溢出异常,并且异常将在第n-1个堆栈级别捕获。现在看到我们释放了1个堆栈级别。现在,finally块中的调用将再次成功,直到第n级,它才可能引发stackoverflow异常。

现在,我们在第n个堆栈级别获得堆栈溢出异常,执行转到finally块,该块再次引发异常,并且此异常在第n-1个级别的finally块中接收。由于无法处理该异常,因此将异常抛出到第n-2个堆栈级别,该堆栈将捕获该异常并重新触发递归。

请注意,到目前为止,我们已经释放了两个堆栈级别。

现在,递归级别再次到达第n个级别,控制权返回到n-3,然后再次到达第n个,然后返回n-4,依此类推。

有时,我们将到达第0个堆栈级别的finally块,并且通过try然后再进入第n个级别,然后最终阻塞,最后将异常抛出给main方法。

堆栈一次又一次地被释放和占用,因此有许多溢出异常和终止时间。

第二部分会影响其他程序: 不,不应该。堆栈大小用完了分配给jvm的空间。操作系统只会为jvm分配有限的堆栈大小。如果需要,也可以通过jvm参数进行控制。

希望这可以澄清。

答案 1 :(得分:0)

这是一个递归方法调用。堆栈调用有一个限制,因此当调用次数达到try块中的限制时,它将引发堆栈溢出异常。异常之后,它将尝试在最后一个块中运行代码。但是由于这种限制,它引发了另一个异常,该异常导致从嵌套方法调用退出,并为另一个方法调用腾出了可用空间。在外层一次又一次地重复这种情况。一旦在堆栈中获得可用空间,它将尝试通过另一个方法调用来填充它。这就是为什么它一直持续到没有内存的原因。

答案 2 :(得分:0)

您基本上是在使用递归。并且递归没有任何终止条件。因此,如果您运行此代码,它将不会终止,直到发生内存错误。 现在说到这里,内存错误是如何发生的?

开始执行程序时,程序在线程中运行(如果程序未设置多线程)。为当前线程分配了堆栈内存。

当函数进行递归调用时,

  • 当前函数暂停执行
  • 当前函数在当前线程堆栈(称为上下文堆栈)中存储环境(局部变量,控件所在的位置,一些内部详细信息等称为执行上下文)。
  • 嵌套函数调用从当前函数开始执行
  • 嵌套函数执行完成后,调用函数从中断点恢复执行。

因此,对于每个递归调用,执行上下文都会存储在上下文堆栈中。而且,如果递归不包含任何终止条件,或者在递归达到终止条件之前内存已满,则将得到StackOverFlowError,因为堆栈内存已为当前执行线程填充。

我认为这将清除您的概念。