Try-finally блок предотвращает StackOverflowError
взгляните на следующие два метода:
public static void foo() {
try {
foo();
} finally {
foo();
}
}
public static void bar() {
bar();
}
под управлением bar()
однозначно приводит к StackOverflowError
, но foo()
нет (программа просто, кажется, идут бесконечно). почему это?
6 ответов:
Он не работает вечно. Каждое переполнение стека приводит к перемещению кода в блок 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 требуется вдвое больше времени, чем глубина стека может быть 10000 или больше. Если вы можете сделать 10,000,000 звонков в секунду, это займет 10^3003 секунд или дольше, чем возраст Вселенной.
когда вы получаете исключение из вызова
foo()
внутриtry
, вы называетеfoo()
сfinally
и снова начать рекурсией. Когда это вызовет еще одно исключение, вы позвонитеfoo()
из другого внутреннегоfinally()
и так далее почти бесконечности.
попробуйте выполнить следующий код:
try { throw new Exception("TEST!"); } finally { System.out.println("Finally"); }
вы обнаружите, что блок finally выполняется перед тем, как выбросить исключение до уровня выше него. (Вывод:
наконец-то
исключение в потоке" main " java.ленг.Исключение: тест! при испытаниях.главный (тест.java: 6)
это имеет смысл, так как, наконец, вызывается прямо перед выходом из метода. Это означает, однако, что как только вы получите, что первый
StackOverflowError
, он будет пытаться бросьте его, но в конце концов должен выполнить первый, так что он работаетfoo()
снова, который получает еще одно переполнение стека, и как таковой работает, наконец, снова. Это продолжается вечно,поэтому исключение никогда не печатается.однако в вашем методе bar, как только возникает исключение, оно просто бросается прямо на уровень выше и будет напечатано
the правильный ответ на вопрос.
это пытается имитировать условия того, что происходит, когда вызов не может произойти, потому что это приведет к переполнению стека. Мне кажется самое сложное, что люди не могут понять, что вызов не происходит, когда он не может произойдет.
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
, и наконец пишет след того, что произошло: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
больше или меньше раз, чем другие)