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 powoduje StackOverflowError
, ale foo()
nie działa (program wydaje się działać bez końca). Dlaczego?
java
recursion
stack-overflow
try-finally
arshajii
źródło
źródło
finally
klauzuli zostaną przeniesione na wyższy poziom. Ale nie wstrzymuj oddechu; liczba wykonanych kroków wyniesie około 2 do (maksymalnej głębokości stosu), a rzucanie wyjątków nie jest też do końca tanie.bar()
.Odpowiedzi:
To nie trwa wiecznie. Każde przepełnienie stosu powoduje przejście kodu do bloku ostatecznie. Problem polega na tym, że zajmie to naprawdę bardzo długo. Kolejność czasu wynosi O (2 ^ N), gdzie N jest maksymalną głębokością stosu.
Wyobraź sobie, że maksymalna głębokość wynosi 5
Aby przepracować każdy poziom w bloku ostatecznie, potrzeba dwa razy więcej, a głębokość stosu może wynosić 10 000 lub więcej. Jeśli wykonasz 10 000 000 połączeń na sekundę, zajmie to 10 ^ 3003 sekund lub więcej niż wiek wszechświata.
źródło
-Xss
, otrzymuję głębokość [150 - 210], więc 2 ^ n kończy się na liczbie [47 - 65] cyfr. Nie będę czekać tak długo, to dla mnie wystarczająco blisko nieskończoności.foo
końcu zakończy się, spowoduje toStackOverflowError
?Kiedy pojawi się wyjątek z wezwaniem
foo()
wewnątrztry
, zadzwonićfoo()
zfinally
i rozpocząć recursing ponownie. Gdy spowoduje to kolejny wyjątek, zadzwoniszfoo()
z innego wewnętrznegofinally()
i tak dalej, prawie bez końca .źródło
foo()
wywołać w końcu po SOE?foo()
wywołania i wywołaszfoo()
wfinally
bloku bieżącegofoo()
wywołania.Spróbuj uruchomić następujący kod:
Przekonasz się, że blok w końcu wykonuje się przed rzuceniem wyjątku na poziom powyżej niego. (Wynik:
Ma to sens, ponieważ w końcu jest wywoływane tuż przed wyjściem z metody. Oznacza to jednak, że gdy zdobędziesz go pierwszy
StackOverflowError
, spróbuje go wyrzucić, ale w końcu musi wykonać najpierw, więc działafoo()
ponownie, co powoduje przepełnienie stosu i jako taki działa w końcu ponownie. Dzieje się tak wiecznie, więc wyjątek nigdy nie jest drukowany.Jednak w metodzie słupkowej, gdy tylko wystąpi wyjątek, jest on po prostu rzucany prosto do poziomu powyżej i zostanie wydrukowany
źródło
W celu dostarczenia uzasadnionego dowodu, że ta WOLA ostatecznie wygasa, oferuję następujący raczej bezsensowny kod. Uwaga: Java nie jest moim językiem, pod żadnym względem najbardziej żywej wyobraźni. I propozycja to tylko do wspierania odpowiedź Piotra, która jest poprawną odpowiedź na pytanie.
Ta próba symulacji warunków tego, co się dzieje, gdy wywołanie NIE może się zdarzyć, ponieważ spowodowałoby to przepełnienie stosu. Wydaje mi się, że najtrudniejszą rzeczą, której ludzie nie rozumieją, jest fakt, że inwokacja nie zdarza się, gdy nie może .
Rezultat tego małego bezsensownego stosu maź jest następujący, a faktyczny wyjątek może być zaskoczeniem; Aha, i 32 try-call (2 ^ 5), co jest całkowicie oczekiwane:
źródło
Naucz się śledzić swój program:
Oto wynik, który widzę:
Jak widać StackOverFlow jest rzucany na niektóre warstwy powyżej, więc możesz wykonać dodatkowe kroki rekurencyjne, aż trafisz na inny wyjątek i tak dalej. To jest nieskończona „pętla”.
źródło
foo
się drugi raz, wfinally
bloku, nie jest już wtry
. Więc chociaż wróci on do stosu i spowoduje więcej przepełnień stosu, po raz drugi po prostu wyrzuci błąd spowodowany przez drugie wywołaniefoo
, zamiast ponownego pogłębiania.Program wydaje się działać wiecznie; faktycznie kończy się, ale zajmuje więcej czasu, im więcej masz miejsca na stosie. Aby udowodnić, że się koń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:Kod:
Możesz spróbować online! (Niektóre biegi mogą dzwonić
foo
więcej lub mniej razy niż inne)źródło