为什么可以从StackOverflowError中恢复?

时间:2014-03-02 13:54:57

标签: java stack-overflow

我很惊讶即使在Java中发生StackOverflowError之后仍然可以继续执行。

我知道StackOverflowError是类Error的子类。 类Error被称为“Throwable的一个子类,表示一个合理的应用程序不应该试图捕获的严重问题。”

这听起来更像是一个推荐而不是一个规则,主张捕获像StackOverflowError这样的错误实际上是允许的,这取决于程序员不这样做的合理性。看,我测试了这段代码,它正常终止。

public class Test
{
    public static void main(String[] args)
    {
        try {
            foo();
        } catch (StackOverflowError e) {
            bar();
        }
        System.out.println("normal termination");
    }

    private static void foo() {
        System.out.println("foo");
        foo();
    }

    private static void bar() {
        System.out.println("bar");
    }
}

这怎么可能?我想当抛出StackOverflowError时,堆栈应该是如此完整,以至于没有空间调用另一个函数。错误处理块是在另一个堆栈中运行,还是在这里发生了什么?

5 个答案:

答案 0 :(得分:117)

当堆栈溢出并抛出StackOverflowError时,通常的异常处理会展开堆栈。展开堆栈意味着:

  • 中止当前活动功能的执行
  • 删除其堆栈帧,继续调用函数
  • 中止来电者的执行
  • 删除其堆栈帧,继续调用函数
  • 等......

...直到异常被捕获。这是正常的(实际上是必要的),并且与抛出异常和原因无关。由于您在第一次调用foo()之外捕获异常,因此填充堆栈的数千个foo堆栈帧已全部展开,并且大部分堆栈可以再次使用。

答案 1 :(得分:23)

抛出StackOverflowError时,堆栈已满。但是,当它被捕获时,所有这些foo调用都已从堆栈中弹出。 bar可以正常运行,因为堆栈不再溢出foo s。 (请注意,我认为JLS不能保证您可以像这样从堆栈溢出中恢复。)

答案 2 :(得分:12)

当StackOverFlow发生时,JVM将弹出到catch,释放堆栈。

在你的例子中,它得到了所有堆叠foo的rids。

答案 3 :(得分:8)

因为堆栈实际上没有溢出。更好的名称可能是AttemptToOverflowStack。基本上它意味着最后一次调整堆栈帧的尝试是错误的,因为堆栈上没有足够的可用空间。堆栈实际上可能有很多空间,只是没有足够的空间。因此,无论操作取决于调用成功(通常是方法调用),都不会被激活,剩下的就是程序处理这个事实。这意味着它与其他任何例外都没有什么不同。实际上,您可以在正在进行调用的函数中捕获异常。

答案 4 :(得分:1)

already been answered一样,可以在捕获StackOverflowError之后执行代码,尤其是调用函数,因为JVM的常规异常处理过程使{{1 }}和throw点,释放堆栈空间供您使用。实验证明情况就是如此。

但是,这与说通常可以从catch中恢复完全不同。

A StackOverflowError IS-A VirtualMachineError,即IS-AN Error。如您所指出的,Java为StackOverflowError提供了一些模糊的建议:

  

表示严重的问题,即合理的应用程序不应试图抓住

并且您可以合理地得出结论,在某些情况下,应该听起来像是抓到Error。请注意,进行一个实验并不能证明总体上可以安全进行。只有Java语言的规则和所用类的规范才能做到这一点。 Error是特殊的异常类,因为 Java语言规范 Java虚拟机规范提供了有关此异常语义的信息。特别是the latter says

  

当内部错误或资源限制阻止Java虚拟机实现实现本章中描述的语义时,它会抛出一个对象,该对象是类VirtualMachineError的子类的实例。该规范无法预测可能会遇到内部错误或资源限制的地方,并且不能准确地规定何时可以报告它们。因此,在Java虚拟机运行期间,可以随时抛出以下定义的VirtualMethodError子类中的任何一个:

...

  •   

    VirtualMethodError:Java虚拟机实现已为线程耗尽了堆栈空间,通常是由于执行程序中的错误导致线程正在进行无数次的递归调用。

关键问题是您“无法预测” StackOverflowError的位置或时间。关于no guarantees不会被扔到何处的信息。例如,您不能依靠它在 entry 上抛出一个方法。可以将其扔到方法中的某个地方。

这种不可预测性可能是灾难性的。由于可以在方法中引发它,因此可以通过该类认为是一个“原子”操作的一系列操作中的一部分抛出,从而使对象处于部分修改,不一致的状态。在对象处于不一致状态的情况下,任何尝试使用该对象都可能导致错误的行为。在所有实际情况下,您都不知道哪个对象处于不一致状态,因此必须假定 no 对象是可信任的。因此,任何恢复操作或尝试在捕获到异常之后继续进行的操作都可能具有错误的行为。因此,唯一安全的方法是捕获StackOverflowError,而是允许程序终止。 (在实践中,您可能会尝试执行一些错误日志记录来帮助进行故障排除,但您不能依靠正常运行该日志记录)。也就是说,您无法从StackOverflowError 中可靠地恢复。