Try-finally块阻止StackOverflowError

ars*_*jii 329 java stack-overflow recursion try-finally

看看以下两种方法:

public static void foo() {
    try {
        foo();
    } finally {
        foo();
    }
}

public static void bar() {
    bar();
}
Run Code Online (Sandbox Code Playgroud)

运行bar()清楚导致a StackOverflowError,但运行foo()没有(程序似乎只是无限期运行).这是为什么?

Pet*_*rey 329

它不会永远运行.每个堆栈溢出都会导致代码移动到finally块.问题是它需要非常长的时间.时间顺序为O(2 ^ N),其中N是最大堆栈深度.

想象一下,最大深度为5

foo() calls
    foo() calls
       foo() calls
           foo() calls
              foo() which fails to call foo()
           finally calls
              foo() which fails to call foo()
       finally
           foo() calls
              foo() which fails to call foo()
           finally calls
              foo() which fails to call foo()
    finally calls
       foo() calls
           foo() calls
              foo() which fails to call foo()
           finally calls
              foo() which fails to call foo()
       finally
           foo() calls
              foo() which fails to call foo()
           finally calls
              foo() which fails to call foo()
finally calls
    foo() calls
       foo() calls
           foo() calls
              foo() which fails to call foo()
           finally calls
              foo() which fails to call foo()
       finally
           foo() calls
              foo() which fails to call foo()
           finally calls
              foo() which fails to call foo()
    finally calls
       foo() calls
           foo() calls
              foo() which fails to call foo()
           finally calls
              foo() which fails to call foo()
       finally
           foo() calls
              foo() which fails to call foo()
           finally calls
              foo() which fails to call foo()
Run Code Online (Sandbox Code Playgroud)

要将每个级别工作到finally块中需要两倍的时间,堆栈深度可以是10,000或更多.如果您每秒可以拨打10,000,000个电话,则需要10 ^ 3003秒或更长的时间.

  • @oldrinb只为你,我把深度增加到5.;) (64认同)
  • 你能展示最大深度为5的树吗? (5认同)
  • 跟着数学,是的.从最后一个堆栈溢出失败的最后一个堆栈溢出将以... stack overflow = P退出.无法抗拒. (5认同)
  • 很好,即使我尝试通过`-Xss`使堆栈尽可能小,我的深度为[150 - 210],因此2 ^ n最终为[47 - 65]位数.不要等那么久,这对我来说已经接近无限. (4认同)
  • 那么,当`foo`终于终止时,它将导致`StackOverflowError`? (4认同)

nin*_*alj 40

当你从调用得到一个异常foo()里面的try,你打电话foo()finally和再次开始递归.当这导致另一个异常时,你会foo()从另一个内部调用finally(),等等几乎无限.

  • 据推测,当堆栈上没有更多空间来调用新方法时,会发送StackOverflowError(SOE).如何在*SOE之后从最终*调用`foo()`? (5认同)
  • @assylias:如果没有足够的空间,你将从最新的`foo()`调用返回,并在你当前的`foo()`调用的`finally`块中调用`foo()`. (4认同)

Ale*_*man 38

尝试运行以下代码:

    try {
        throw new Exception("TEST!");
    } finally {
        System.out.println("Finally");
    }
Run Code Online (Sandbox Code Playgroud)

你会发现finally块在抛出异常直到它上面的级别之前执行.(输出:

最后

线程"main"中的异常java.lang.Exception:TEST!在test.main(test.java:6)

这是有道理的,因为最终在退出方法之前被调用.但是,这意味着,一旦你得到它StackOverflowError,它将尝试抛出它,但最后必须首先执行,因此它foo()再次运行,这将获得另一个堆栈溢出,并因此最终再次运行.这种情况永远发生,因此异常永远不会被打印出来.

然而,在你的bar方法中,一旦发生异常,它就会直接抛到上面的级别,然后打印出来

  • 投反对票。“永远发生”是错误的。查看其他答案。 (2认同)

Who*_*aig 26

为了提供合理的证据表明这将最终终止,我提供以下相当无意义的代码.注意:Java不是我的语言,在任何一个最生动的想象中.我提出这个问题只是为了支持彼得的答案,这是对这个问题正确答案.

这会尝试模拟调用无法发生时所发生的情况,因为它会引入堆栈溢出.在我看来,最难的事情人们都未能把握,当它的调用不会发生不能发生.

public class Main
{
    public static void main(String[] args)
    {
        try
        {   // invoke foo() with a simulated call depth
            Main.foo(1,5);
        }
        catch(Exception ex)
        {
            System.out.println(ex.toString());
        }
    }

    public static void foo(int n, int limit) throws Exception
    {
        try
        {   // simulate a depth limited call stack
            System.out.println(n + " - Try");
            if (n < limit)
                foo(n+1,limit);
            else
                throw new Exception("StackOverflow@try("+n+")");
        }
        finally
        {
            System.out.println(n + " - Finally");
            if (n < limit)
                foo(n+1,limit);
            else
                throw new Exception("StackOverflow@finally("+n+")");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这个小小的无知堆的输出如下,实际的例外可能会让人感到惊讶; 哦,还有32次试用(2 ^ 5),完全可以预料到:

1 - Try
2 - Try
3 - Try
4 - Try
5 - Try
5 - Finally
4 - Finally
5 - Try
5 - Finally
3 - Finally
4 - Try
5 - Try
5 - Finally
4 - Finally
5 - Try
5 - Finally
2 - Finally
3 - Try
4 - Try
5 - Try
5 - Finally
4 - Finally
5 - Try
5 - Finally
3 - Finally
4 - Try
5 - Try
5 - Finally
4 - Finally
5 - Try
5 - Finally
1 - Finally
2 - Try
3 - Try
4 - Try
5 - Try
5 - Finally
4 - Finally
5 - Try
5 - Finally
3 - Finally
4 - Try
5 - Try
5 - Finally
4 - Finally
5 - Try
5 - Finally
2 - Finally
3 - Try
4 - Try
5 - Try
5 - Finally
4 - Finally
5 - Try
5 - Finally
3 - Finally
4 - Try
5 - Try
5 - Finally
4 - Finally
5 - Try
5 - Finally
java.lang.Exception: StackOverflow@finally(5)
Run Code Online (Sandbox Code Playgroud)


Kar*_*ath 23

学会追踪你的计划:

public static void foo(int x) {
    System.out.println("foo " + x);
    try {
        foo(x+1);
    } 
    finally {
        System.out.println("Finally " + x);
        foo(x+1);
    }
}
Run Code Online (Sandbox Code Playgroud)

这是我看到的输出:

[...]
foo 3439
foo 3440
foo 3441
foo 3442
foo 3443
foo 3444
Finally 3443
foo 3444
Finally 3442
foo 3443
foo 3444
Finally 3443
foo 3444
Finally 3441
foo 3442
foo 3443
foo 3444
[...]
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,StackOverFlow在上面的某些层上抛出,因此您可以执行其他递归步骤,直到遇到另一个异常,依此类推.这是一个无限的"循环".

  • 它实际上并不是无限循环,如果你足够耐心,它最终会终止.不过,我不会屏住呼吸. (11认同)
  • 我认为这是无限的.每次达到最大堆栈深度时,它都会抛出异常并展开堆栈.然而,在最后它再次调用Foo导致它再次重用它刚刚恢复的堆栈空间.它将来回抛出异常,然后回到Dow堆栈,直到它再次发生.永远. (4认同)