为什么它不会因堆栈溢出错误而终止?

Why doesn't it terminate with a stack overflow error?

我是从一本名为 Java Puzzlers 的书中看到这个程序的。这本书解释了它的行为,但我无法得到所有内容。

书上说以下程序在 1.7 × 10291 后终止,假设有一台计算机正在处理它。它还说它抛出 WhosebugError 21,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();
        }
    }
}

我想了解额外堆栈的来源以及该程序是否会影响机器的其他程序 运行 消耗其堆栈?

书上有技术说明。通俗的解释是什么,像非常feynmanly?

这是一个递归方法调用。

堆栈调用是有限制的,所以当在try块中调用次数达到限制时,它会抛出堆栈溢出异常。

异常后,它会尝试运行 final 块中的代码。但是由于限制,它引发了另一个异常,导致退出嵌套方法调用并为另一个方法调用释放了 space。

这样的场景在外层一次又一次的重复。一旦它在堆栈中获得空闲 space,它就会尝试通过另一个方法调用来填充它。这就是它一直持续到没有内存的原因。

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

进入正题,这里怎么会出现内存错误?

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

当函数进行递归调用时,

  • 当前函数暂停执行
  • 当前函数在当前线程栈中存储了环境(局部变量,现在控件在哪里,一些内部细节等称为执行上下文),已知作为 上下文堆栈
  • 嵌套函数调用从当前函数开始执行
  • 当嵌套函数执行完成后,调用函数从中断点继续执行。

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

让我们将堆栈底部索引为 0 并在堆栈级别 1、2、3 等处进行后续调用(因为它是递归的)。每次调用 workHard 都会将堆栈索引增加 1,每次调用 return 应该将堆栈长度减少 1。

由于没有任何return语句,这段代码形成了一个无限递归,唯一跳出这个递归循环的方法就是一个堆栈溢出异常来打破这个循环。

考虑没有try/finally的修改后的代码:

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块。

由于我们的堆栈大小已经非常有限,这个finally 块也会抛出一个堆栈溢出异常,该异常会在第n-1 层堆栈被捕获。现在看到我们已经释放了一层堆栈。现在 finally 块中的调用将再次成功并在它可以抛出 Whosebug 异常之前进入第 n 级。

现在我们得到第n层栈的栈溢出异常。执行转到 finally 块,它再次抛出异常,并且在第 n-1 级的 finally 块中接收到此异常。由于没有办法处理异常,异常被抛到第n-2层栈,捕获异常并重新触发递归。

请注意,我们现在已经释放了两个堆栈级别。

现在递归级别再次到达第 n 级别并控制 returns 回到第 n-3 并再次到达第 n 并且 returning 回到第 n-4 并且依此类推

在某个时候,我们将到达第 0 层堆栈的 finally 块,然后通过 try[=54 再次到达第 n 层=] 然后是 finally 块,最后将异常抛出给 main 方法。

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

来到第二部,会不会影响其他节目?

不,不应该。堆栈大小用完了分配给 JVM 的 space。 OS 只会将有限的堆栈大小分配给 JVM。如果需要,也可以通过 JVM 参数控制。