java Try-finally 块可防止 StackOverflowError
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/12438786/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Try-finally block prevents StackOverflowError
提问by arshajii
Take a look at the following two methods:
看看下面两种方法:
public static void foo() {
try {
foo();
} finally {
foo();
}
}
public static void bar() {
bar();
}
Running bar()
clearly results in a StackOverflowError
, but running foo()
does not (the program just seems to run indefinitely). Why is that?
运行bar()
显然会导致StackOverflowError
,但运行foo()
不会(该程序似乎无限期地运行)。这是为什么?
回答by Peter Lawrey
It doesn't run forever. Each stack overflow causes the code to move to the finally block. The problem is that it will take a really, really long time. The order of time is O(2^N) where N is the maximum stack depth.
它不会永远运行。每次堆栈溢出都会导致代码移动到 finally 块。问题是这将需要非常非常长的时间。时间顺序是 O(2^N),其中 N 是最大堆栈深度。
Imagine the maximum depth is 5
想象一下最大深度是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()
To work each level into the finally block take twice as long an the stack depth could be 10,000 or more. If you can make 10,000,000 calls per second, this will take 10^3003 seconds or longer than the age of the universe.
将每个级别工作到 finally 块需要两倍的时间,堆栈深度可能是 10,000 或更多。如果每秒可以进行 10,000,000 次调用,这将花费 10^3003 秒或比宇宙年龄更长的时间。
回答by ninjalj
When you get an exception from the invocation of foo()
inside the try
, you call foo()
from finally
and start recursing again. When that causes another exception, you'll call foo()
from another inner finally()
, and so on almost ad infinitum.
当您从foo()
内部的调用中得到异常时try
,您调用foo()
fromfinally
并再次开始递归。当这导致另一个异常时,您foo()
将从另一个内部调用finally()
,依此类推,几乎无穷无尽。
回答by Alex Coleman
Try running the following code:
尝试运行以下代码:
try {
throw new Exception("TEST!");
} finally {
System.out.println("Finally");
}
You will find that the finally block executes before throwing an Exception up to the level above it. (Output:
您会发现 finally 块在向其上一级抛出异常之前执行。(输出:
Finally
Exception in thread "main" java.lang.Exception: TEST! at test.main(test.java:6)
最后
线程“main”中的异常 java.lang.Exception: TEST! 在 test.main(test.java:6)
This makes sense, as finally is called right before exiting the method. This means, however, that once you get that first StackOverflowError
, it will try to throw it, but the finally must execute first, so it runs foo()
again, which gets another stack overflow, and as such runs finally again. This keeps happening forever, so the exception is never actually printed.
这是有道理的,因为 finally 在退出方法之前被调用。然而,这意味着一旦你得到 first StackOverflowError
,它就会尝试抛出它,但 finally 必须首先执行,所以它foo()
再次运行,这会导致另一个堆栈溢出,因此 finally 再次运行。这种情况永远发生,因此实际上从未打印过异常。
In your bar method however, as soon as the exception occurs, it is just thrown straight up to the level above, and will be printed
然而,在你的 bar 方法中,一旦发生异常,它就会直接抛出到上面的级别,并会被打印出来
回答by WhozCraig
In effort to provide reasonable evidence that this WILL eventually terminate, I offer the following rather meaningless code. Note: Java is NOT my language, by any stretch of the most vivid imagination. I proffer this up only to support Peter's answer, which is thecorrect answer to the question.
为了提供合理的证据证明这将最终终止,我提供了以下相当无意义的代码。注意:Java 不是我的语言,最生动的想象。我提出这一点只是为了支持彼得的回答,这是对问题的正确答案。
This attempts to simulate the conditions of what happens when an invoke can NOT happen because it would introduce a stack overflow. It seems to me the hardest thing people are failing to grasp in that the invoke does not happen when it cannothappen.
这试图模拟当调用不能发生时发生的情况,因为它会引入堆栈溢出。在我看来,人们无法理解的最难的事情是调用在不可能发生时不会发生。
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+")");
}
}
}
The output of this little pointless pile of goo is the following, and the actual exception caught may come as a surprise; Oh, and 32 try-calls (2^5), which is entirely expected:
这个毫无意义的小堆 goo 的输出如下,实际捕获的异常可能会令人惊讶;哦,还有 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)
回答by Karoly Horvath
Learn to trace your program:
学习跟踪您的程序:
public static void foo(int x) {
System.out.println("foo " + x);
try {
foo(x+1);
}
finally {
System.out.println("Finally " + x);
foo(x+1);
}
}
This is the output I see:
这是我看到的输出:
[...]
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
[...]
As you can see the StackOverFlow is thrown at some layers above, so you can do additional recursion steps till you hit another exception, and so on. This is an infinite "loop".
正如您所看到的 StackOverFlow 在上面的某些层被抛出,因此您可以执行额外的递归步骤,直到遇到另一个异常,依此类推。这是一个无限的“循环”。
回答by Vitruvius
The program merely seems to run forever; it actually terminates, but it takes exponentially more time the more stack space you have. To prove that it finishes, I wrote a program that first depletes most of the available stack space, and then calls foo
, and finally writes a trace of what happened:
该程序似乎只是永远运行;它实际上终止了,但是您拥有的堆栈空间越多,所需的时间就越多。为了证明它完成了,我写了一个程序,首先耗尽大部分可用的堆栈空间,然后调用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)
...
The code:
代码:
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);
}
});
}
}
You can try it online!(Some runs might call foo
more or fewer times than others)
你可以上网试试!(某些运行可能foo
比其他运行调用更多或更少的次数)