为什么可以从StackOverflowError中恢复?

use*_*796 99 java stack-overflow

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

我知道这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");
    }
}
Run Code Online (Sandbox Code Playgroud)

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

小智 118

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

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

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

  • 关键是最里面的“foo”以未定义的状态终止,因此它可能接触到的任何对象都必须被假定为已损坏。由于您不知道堆栈溢出发生在哪个函数中,只知道它必须是捕获它的“try”块的后代,因此可以通过从那里可访问的任何方法修改的任何对象现在都是可疑的。通常,找出发生的情况并尝试修复它是不值得的。 (2认同)
  • @delnan,我认为答案是不完整的,但没有详细说明为什么这是一个坏主意.与显式抛出的异常的区别在于,即使在编写异常安全代码时也无法预测到错误. (2认同)

use*_*ica 23

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


Nic*_*oux 12

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

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


jmo*_*eno 8

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


Rae*_*ald 7

正如已经回答的那样,在捕获 a 之后,可以执行代码,特别是调用函数,因为 JVM 的正常异常处理过程会展开和点StackOverflowError之间的堆栈,释放堆栈空间供您使用。你的实验证实了这一点。throwcatch

然而,这并不完全等同于说一般情况下可以StackOverflowError.

一个StackOverflowErrorIS-A VirtualMachineError,即 IS-AN Error。正如您所指出的,Java 为以下内容提供了一些模糊的建议Error

表示合理的应用程序不应试图捕获的严重问题

并且您合理地得出结论,听起来应该在某些情况下捕获Error可能没问题。请注意,进行一项实验并不表明某件事通常是安全的。只有 Java 语言的规则和您使用的类的规范才能做到这一点。AVirtualMachineError是一类特殊的异常,因为Java 语言规范Java 虚拟机规范提供了有关此异常的语义的信息。特别是,后者说

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

...

  • StackOverflowError:Java 虚拟机实现已耗尽线程的堆栈空间,通常是因为执行程序中出现错误而导致线程执行无限数量的递归调用。

关键的问题是你“无法预测”aStackOverflowError将在何时何地被抛出。无法保证它不会被扔到哪里。例如,您不能依赖在进入方法时抛出它。它可以被抛出到方法内的某个点。

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