【问题标题】:Why is it possible to recover from a StackOverflowError?为什么可以从 StackOverflowError 中恢复?
【发布时间】:2014-04-03 10:08:01
【问题描述】:

我很惊讶即使在 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 被抛出时,堆栈应该已经满了,以至于没有空间调用另一个函数。错误处理块是否在不同的堆栈中运行,或者这里发生了什么?

【问题讨论】:

  • 我总是在 StackOverflow 上出错。但这并不能阻止我回来。
  • Yo dawg...我听说你喜欢堆栈溢出,所以我们在你的 stackoverflow.com 中放置了一个堆栈溢出!
  • 因为现代架构使用帧指针来促进展开堆栈,甚至是部分堆栈。只要执行此操作的代码+上下文不必从堆栈中动态分配,就不会有问题。

标签: java stack-overflow


【解决方案1】:

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

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

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

【讨论】:

  • @fge 随意编辑,我考虑过分节但找不到合适的地方。
  • 你可以使用要点...我不愿意编辑其他人的帖子;)
  • 关键是最里面的foo 以未定义状态终止,因此它可能接触到的任何对象都必须假定为已损坏。由于您不知道堆栈溢出发生在哪个函数中,只是它必须是捕获它的try 块的后代,因此现在可以怀疑任何可以被任何可从那里访问的方法修改的对象。通常不值得找出发生了什么并尝试解决它。​​
  • @delnan,我认为答案不完整,没有详细说明为什么这是一个坏主意。与显式抛出异常的区别在于,即使编写异常安全代码,也无法预料到Errors。
  • @SimonRichter 不,这个问题非常具体。这不是关于处理Errors。 OP only 询问StackOverflowError,它询问处理 this 错误的具体问题:方法如何调用 not捕获此错误时失败。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

    在您的示例中,它摆脱了所有堆叠的 foo。

    【讨论】:

      【解决方案4】:

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

      【讨论】:

      • 请注意,如果您这样做,您的异常处理程序不需要比可用的更多堆栈空间!
      【解决方案5】:

      already been answered 一样,在捕获StackOverflowError 之后可以执行代码,尤其是调用函数,因为JVM 的正常异常处理过程会展开throw 和@987654329 之间的堆栈@点,释放堆栈空间供您使用。你的实验证实了这一点。

      但是,这与说一般情况下可以从StackOverflowError恢复并不完全相同。

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

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

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

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

      ...

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

      关键问题是您“无法预测”StackOverflowError 将在何时何地被抛出。有no guarantees关于它不会被抛出的地方。例如,您不能依赖它被抛出到方法的 entry 上。它可以被扔在一个方法的某个点上。

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

      【讨论】:

        猜你喜欢
        • 2017-08-18
        • 2012-07-13
        • 1970-01-01
        • 1970-01-01
        • 2021-08-10
        • 2013-06-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多