Try-w końcu blokuje StackOverflowError
Spójrz na następujące dwie metody:
public static void foo() {
try {
foo();
} finally {
foo();
}
}
public static void bar() {
bar();
}
Uruchamianie bar()
wyraźnie skutkuje StackOverflowError
, ale uruchamianie foo()
nie (program wydaje się działać w nieskończoność). Dlaczego?
6 answers
Nie działa wiecznie. Każde przepełnienie stosu powoduje przeniesienie kodu do bloku finally. Problem w tym, że zajmie to naprawdę, naprawdę dużo czasu. Kolejność czasu wynosi O (2^N), gdzie N jest maksymalną głębokością stosu.
Wyobraź sobie, że maksymalna głębokość wynosi 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()
Do pracy na każdym poziomie W końcu bloku zajmuje dwa razy więcej czasu niż głębokość stosu może być 10,000 lub więcej. Jeśli możesz wykonać 10 000 000 połączeń na sekundę, zajmie to 10^3003 sekund lub dłużej niż wiek wszechświat.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-15 17:17:52
Kiedy otrzymujesz wyjątek od wywołania foo()
wewnątrz try
, wywołujesz foo()
z finally
i zaczynasz ponownie rekurencyjnie. Gdy spowoduje to inny wyjątek, wywołasz foo()
z innego wewnętrznego finally()
, i tak dalej prawie ad infinitum.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-15 17:02:32
Spróbuj uruchomić następujący kod:
try {
throw new Exception("TEST!");
} finally {
System.out.println("Finally");
}
Przekonasz się, że blok finally jest wykonywany przed wyrzuceniem wyjątku do poziomu powyżej. (Wyjście:
Wreszcie
Wyjątek w wątku "main" java.lang.Wyjątek: TEST! w teście.główna (test.java: 6)
To ma sens, jak w końcu nazywa się tuż przed zakończeniem metody. Oznacza to jednak, że gdy zdobędziesz ten pierwszy StackOverflowError
, spróbuje go rzucić, ale w końcu musi wykonać najpierw uruchamia się foo()
ponownie, co powoduje przepełnienie stosu i jako takie uruchamia się ponownie. Dzieje się to w nieskończoność, więc wyjątek nigdy nie jest drukowany.
W Twojej metodzie bar jednak, jak tylko wystąpi wyjątek, jest on po prostu wyrzucany prosto do poziomu powyżej i zostanie wydrukowany
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-15 15:54:39
W celu dostarczenia rozsądnych dowodów, że to ostatecznie zakończy się, oferuję następujący, raczej bezsensowny kod. Uwaga: Java nie jest moim językiem, przez żaden odcinek najbardziej żywej wyobraźni. Proponuję to tylko po to, aby poprzeć odpowiedź Piotra, która jest poprawną odpowiedzią na pytanie.
To próba symulowania warunków, co się dzieje, gdy wywołanie nie może się wydarzyć, ponieważ wprowadziłoby przepełnienie stosu. Wydaje mi się, że najtrudniej jest ludziom nie rozumieją, że wywołanie nie dzieje się wtedy, gdy nie może się zdarzyć.
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+")");
}
}
}
Wynik tej małej bezsensownej kupy mazi jest następujący, a faktyczny wyłapany wyjątek może być zaskoczeniem; Oh, I 32 TRY-calls (2^5), co jest całkowicie oczekiwane:
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)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-18 22:08:17
Naucz się śledzić swój 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);
}
}
Oto wyjście, które widzę:
[...]
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
[...]
Jak widzisz, przepływ stosu jest wyrzucany na niektóre warstwy powyżej, więc możesz wykonać dodatkowe kroki rekurencji, aż do uzyskania kolejnego wyjątku, i tak dalej. Jest to nieskończona "pętla".
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-15 16:14:31
Program wydaje się działać w nieskończoność; w rzeczywistości kończy się, ale zajmuje wykładniczo więcej czasu, tym więcej miejsca na stosie. Aby udowodnić, że to się skończy, napisałem program, który najpierw wyczerpuje większość dostępnego miejsca na stosie, a następnie wywołuje foo
, a na koniec zapisuje ślad tego, co się stało:
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)
...
Kod:
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);
}
});
}
}
Możesz spróbować online! (niektóre biegi mogą wywoływać foo
więcej lub mniej razy niż inne)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-11 00:08:28