为什么可以从StackOverflowError中恢复?


100

我很惊讶即使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");
    }
}

怎么会这样?我认为,当引发StackOverflowError时,堆栈应该已满,以至于没有空间可以调用另一个函数。错误处理块是在其他堆栈中运行还是在这里发生了什么?



57
我一直在StackOverflow上出错。但这并不能阻止我回来。

10
da,我听说您喜欢堆栈溢出,所以我们在您的stackoverflow.com中放入了堆栈溢出!
皮埃尔·亨利

因为现代体系结构使用框架指针来简化堆栈(甚至是部分堆栈)的展开。只要不必在堆栈外动态分配要执行的代码和上下文,就不会有问题。
RBarryYoung 2014年

Answers:


119

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

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

...直到发现异常。这是正常的(实际上是必要的),并且与引发哪个异常以及原因无关。由于您是在第一次调用之外捕获到异常的foo(),因此foo已清除了填充堆栈的数千个堆栈帧,并且大部分堆栈都可以再次使用。


1
@fge随时进行编辑,我考虑过一个段落中断,但找不到适合它的地方。

1
您可以使用要点...我不愿意编辑其他人的帖子;)
fge

1
关键是最里面的foo对象以不确定的状态终止,因此必须触摸它可能碰到的任何对象。您不知道堆栈溢出发生在哪个函数中,只是它必须是try捕获它的块的后代,所以现在怀疑可以用从那里可以到达的任何方法修改的任何对象。通常,找出发生的情况并尝试修复它是不值得的。
Simon Richter 2014年

2
@delnan,我认为答案是不完整的,没有详细说明为什么这是一个坏主意。与显式引发的异常的区别在于,Error即使编写异常安全代码也无法预期s。
西蒙·里希特

1
@SimonRichter不,这个问题很具体。这与处理s 无关Error。该OP是要求只有StackOverflowError,并且它要求的处理具体事情这个错误:怎么能一个方法调用没有时,抓住了这个错误失败。
Bakuriu 2014年

23

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



8

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


1
请注意,如果您执行此操作,则异常处理程序不需要的堆栈空间将超过可用的堆栈空间!
文斯

2

正如已经回答的那样,有可能在捕获到错误后执行代码,尤其是调用函数。StackOverflowError因为正常的异常处理JVM的过程解开的堆栈throwcatch点,释放堆栈空间供您使用。实验证明情况就是如此。

但是,这与说总体上可以 恢复StackOverflowError

StackOverflowErrorIS-A VirtualMachineError,这是-ANError。如您所指出的,Java为Error:提供了一些模糊的建议:

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

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

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

...

  • StackOverflowError:Java虚拟机实现的线程堆栈空间不足,通常是由于执行程序中的错误导致线程正在无限制地进行递归调用。

关键问题是您“无法预测”将在何处或何时StackOverflowError将其抛出。有没有保证有关,它不会被抛出。您不能依靠它在进入时被抛出方法时该错误。它可以在一个点被抛出的方法。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.