【问题标题】:Why doesn't it terminate with a stack overflow error? [duplicate]为什么它不会因堆栈溢出错误而终止? [复制]
【发布时间】:2020-07-09 04:33:33
【问题描述】:

我是从一本名为 Java Puzzlers 的书中发现这个程序的。这本书解释了它的行为,但我无法得到一切。

书中说以下程序在 1.7 × 10291终止,假设有一台计算机正在处理它。它还说它抛出 StackOverflowError 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

【问题讨论】:

  • 到目前为止的回答很有帮助,谢谢大家,能否请您也添加一些视觉线索,以便我和未来的读者很容易理解

标签: java recursion try-catch puzzle


【解决方案1】:

让我们将栈底索引为 0 和后续调用(因为它是递归的)在栈级别 1、2、3 等等。每次调用 workHard 都会将堆栈索引增加 1,并且每次返回应该将堆栈长度减少 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 层我们得到第一个堆栈溢出异常,同时对第 n + 1 层堆栈进行递归调用.

对于来自 try 块的每个堆栈溢出异常,下一行执行将是在 finally 块中调用 workHard。在第 n 级堆栈溢出异常的峰值,将捕获该异常并执行到第 n 级的 finally 块。

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

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

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

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

在某个时间,我们将到达第 0 层堆栈的 finally 块,然后再次通过 try 进入第 n 层,然后 finally 在最终将异常抛出到 main 方法之前阻塞。

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

进入第二部分,会不会影响其他程序?

不,不应该。堆栈大小用尽了分配给 JVM 的空间。操作系统只会将有限的堆栈大小分配给 JVM。如果需要,也可以通过 JVM 参数进行控制。

【讨论】:

  • Shankul Jain 非常有帮助,请您添加一些视觉线索,仅通过阅读很难理解第 n 件事
【解决方案2】:

这是一个递归方法调用。

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

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

这种场景在外层一次又一次地重复。一旦它在堆栈中获得一个空闲空间,它就会尝试通过另一个方法调用来填充它。这就是为什么它会一直持续到没有记忆。

【讨论】:

  • 这是我已经知道的,我想知道堆栈是否曾经溢出堆栈来进行更多调用,以及一旦最终抛出异常为什么它不终止。
  • 我编辑了答案,但请注意 StackOverflow 是一个典型的例外,您可以处理 ti 或强制在最终块退出函数之前执行它。
  • 请注意,StackOverflow 并不意味着终止或“内存不足”。它只是说达到了限制,所以你应该处理它。
  • 让我解释一下,是的,当 StackOverflow 发生时,它说你不允许调用另一个方法,但如果有一个最终块它会执行。由于在最后一个块中调用方法时仍然存在禁止调用条件,因此它会再次引发异常。并转到另一个方法调用。此时显示堆栈中的空间用于另一个方法调用。
【解决方案3】:

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

现在言归正传,这里怎么会出现内存错误?

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

当一个函数进行递归调用时,

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

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

【讨论】:

  • 我以为只有一个堆栈,所有方法调用都存储在其中,这就是它溢出的原因,如果我没记错的话,你是说每次调用都会创建一个堆栈,所以堆栈不应该溢出,错误应该是别的东西,但它会抛出我在问题中提到的stackoverflow错误
  • Java 每个线程使用一个堆栈;这就是它将放置调用上下文的地方。
  • 对不起,我的错。每个线程使用一个堆栈。它为所有函数调用共享。我已经更新了我的答案。
猜你喜欢
  • 2011-10-22
  • 1970-01-01
  • 2019-01-09
  • 2011-04-23
  • 2017-01-13
  • 2019-12-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多