看一下以下两种方法:
public static void foo() {
try {
foo();
} finally {
foo();
}
}
public static void bar() {
bar();
}
bar()
清楚地运行会导致StackOverflowError
,但foo()
不会运行(该程序似乎无限期地运行)。这是为什么?
bar()
。
看一下以下两种方法:
public static void foo() {
try {
foo();
} finally {
foo();
}
}
public static void bar() {
bar();
}
bar()
清楚地运行会导致StackOverflowError
,但foo()
不会运行(该程序似乎无限期地运行)。这是为什么?
bar()
。
Answers:
它不会永远运行。每次堆栈溢出都会导致代码移至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()
要使每个级别进入“ finally”块,需要两倍的时间,而堆栈深度可能是10,000或更大。如果您每秒可以进行10,000,000次呼叫,那么这将花费10 ^ 3003秒或更长的时间。
-Xss
的深度,也得到[150-210]的深度,所以2 ^ n最终是[47-65]位数字。不用等那么久,对我来说这已经足够接近无限。
foo
最后一次终止的一天结束时,它会导致StackOverflowError
?
当你从调用得到一个异常foo()
里面的try
,你打电话foo()
从finally
和再次开始递归。当这导致另一个异常时,您foo()
将从另一个内部调用finally()
,依此类推,几乎是ad infinitum。
foo()
最终称呼?
foo()
调用返回,并foo()
在finally
当前foo()
调用的块中进行调用。
尝试运行以下代码:
try {
throw new Exception("TEST!");
} finally {
System.out.println("Finally");
}
您会发现在将Exception抛出到最高级别之前,执行了finally块。(输出:
最后
线程“主”中的异常java.lang.Exception:TEST!在test.main(test.java:6)
这是有道理的,因为在退出该方法之前将最终调用该方法。但是,这意味着,一旦您首先获得它StackOverflowError
,它将尝试将其抛出,但是必须首先执行finally,因此它将运行foo()
再次,这将导致另一个堆栈溢出,因此最终将再次运行。这一直持续发生,因此永远不会实际打印异常。
但是,在您的bar方法中,一旦发生异常,它将被直接抛出到上面的级别,并将被打印
为了提供合理的证据证明此方法将最终终止,我提供了以下无意义的代码。注意:在任何最生动的想象中,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+")");
}
}
}
这堆毫无意义的小东西的输出如下,所捕获的实际异常可能会令人惊讶。哦,还有32个try-calls(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)
了解如何跟踪程序:
public static void foo(int x) {
System.out.println("foo " + x);
try {
foo(x+1);
}
finally {
System.out.println("Finally " + x);
foo(x+1);
}
}
这是我看到的输出:
[...]
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
[...]
如您所见,在上面的某些层上抛出了StackOverFlow,因此您可以执行其他递归步骤,直到遇到另一个异常,依此类推。这是一个无限的“循环”。
foo
第二次在finally
块中调用时,它不再位于try
。因此,虽然它将退回到堆栈并一次产生更多的堆栈溢出,但是第二次它将只是重新抛出第二次调用所产生的错误foo
,而不是重新加深。
该程序似乎永远运行。它实际上会终止,但是您拥有更多的堆栈空间会花费更多的时间。为了证明它完成了,我编写了一个程序,该程序首先耗尽了大部分可用的堆栈空间,然后调用foo
,最后写了一下发生的情况:
foo 1
foo 2
foo 3
Finally 3
Finally 2
foo 3
Finally 3
Finally 1
foo 2
foo 3
Finally 3
Finally 2
foo 3
Finally 3
Exception in thread "main" java.lang.StackOverflowError
at Main.foo(Main.java:39)
at Main.foo(Main.java:45)
at Main.foo(Main.java:45)
at Main.foo(Main.java:45)
at Main.consumeAlmostAllStack(Main.java:26)
at Main.consumeAlmostAllStack(Main.java:21)
at Main.consumeAlmostAllStack(Main.java:21)
...
编码:
import java.util.Arrays;
import java.util.Collections;
public class Main {
static int[] orderOfOperations = new int[2048];
static int operationsCount = 0;
static StackOverflowError fooKiller;
static Error wontReachHere = new Error("Won't reach here");
static RuntimeException done = new RuntimeException();
public static void main(String[] args) {
try {
consumeAlmostAllStack();
} catch (RuntimeException e) {
if (e != done) throw wontReachHere;
printResults();
throw fooKiller;
}
throw wontReachHere;
}
public static int consumeAlmostAllStack() {
try {
int stackDepthRemaining = consumeAlmostAllStack();
if (stackDepthRemaining < 9) {
return stackDepthRemaining + 1;
} else {
try {
foo(1);
throw wontReachHere;
} catch (StackOverflowError e) {
fooKiller = e;
throw done; //not enough stack space to construct a new exception
}
}
} catch (StackOverflowError e) {
return 0;
}
}
public static void foo(int depth) {
//System.out.println("foo " + depth); Not enough stack space to do this...
orderOfOperations[operationsCount++] = depth;
try {
foo(depth + 1);
} finally {
//System.out.println("Finally " + depth);
orderOfOperations[operationsCount++] = -depth;
foo(depth + 1);
}
throw wontReachHere;
}
public static String indent(int depth) {
return String.join("", Collections.nCopies(depth, " "));
}
public static void printResults() {
Arrays.stream(orderOfOperations, 0, operationsCount).forEach(depth -> {
if (depth > 0) {
System.out.println(indent(depth - 1) + "foo " + depth);
} else {
System.out.println(indent(-depth - 1) + "Finally " + -depth);
}
});
}
}
您可以在线尝试!(某些跑步次数可能foo
比其他次数多或少)
finally
子句期间引发的错误将传播到下一个级别。但是,不要屏住呼吸。所采取的步骤数将约为最大堆栈深度的2倍,并且抛出异常也不是那么便宜。