Jestem zaskoczony, jak można kontynuować wykonywanie nawet po StackOverflowError
wystąpieniu a w Javie.
Wiem, że StackOverflowError
jest to podklasa klasy Error. Klasa Error jest opisana jako „podklasa Throwable, która wskazuje na poważne problemy, których rozsądna aplikacja nie powinna próbować wychwycić”.
Brzmi to bardziej jak zalecenie niż reguła, zakładając, że przechwytywanie błędu takiego jak StackOverflowError jest w rzeczywistości dozwolone i zależy od rozsądku programisty, aby tego nie robić. Widzisz, przetestowałem ten kod i kończy się normalnie.
public class Test
{
public static void main(String[] args)
{
try {
foo();
} catch (StackOverflowError e) {
bar();
}
System.out.println("normal termination");
}
private static void foo() {
System.out.println("foo");
foo();
}
private static void bar() {
System.out.println("bar");
}
}
Jak to może być? Myślę, że do czasu wyrzucenia StackOverflowError stos powinien być tak zapełniony, że nie ma miejsca na wywołanie innej funkcji. Czy blok obsługi błędów działa na innym stosie, czy co się tutaj dzieje?
źródło
Odpowiedzi:
Gdy stos przepełnia się i
StackOverflowError
zostaje wyrzucony, zwykła obsługa wyjątków rozwija stos. Rozwinięcie stosu oznacza:... dopóki wyjątek nie zostanie przechwycony. Jest to normalne (w rzeczywistości konieczne) i niezależne od tego, który wyjątek został zgłoszony i dlaczego. Ponieważ wychwytujesz wyjątek poza pierwszym wywołaniem funkcji
foo()
, tysiącefoo
ramek stosu, które wypełniły stos, zostały rozwinięte i większość stosu może być ponownie wykorzystana.źródło
foo
kończy się nieokreślonym stanem, więc każdy obiekt, którego mógł dotknąć, należy założyć, że jest uszkodzony. Ponieważ nie wiesz, w której funkcji wystąpiło przepełnienie stosu, tylko że musi to być potomektry
bloku, który go przechwycił, każdy obiekt, który można zmodyfikować dowolną dostępną z niego metodą, jest teraz podejrzany. Zwykle nie warto dowiedzieć się, co się stało i spróbować to naprawić.Error
że nie można przewidzieć s, nawet podczas pisania kodu bezpiecznego dla wyjątków.Error
s. OP pyta tylko oStackOverflowError
i pyta o konkretną rzecz dotyczącą obsługi tego błędu: w jaki sposób wywołanie metody może nie zawieść, gdy ten błąd zostanie przechwycony.Gdy zostanie zgłoszony StackOverflowError, stos jest pełny. Jednak gdy zostanie złapany , wszystkie te
foo
wywołania zostały usunięte ze stosu.bar
może działać normalnie, ponieważ stos nie jest już przepełnionyfoo
s. (Zauważ, że nie sądzę, że JLS gwarantuje, że możesz odzyskać od przepełnienia stosu w ten sposób.źródło
Gdy wystąpi StackOverFlow, JVM przejdzie do zaczepu, uwalniając stos.
W Twoim przykładzie usuwa wszystkie skumulowane foo.
źródło
Ponieważ stos w rzeczywistości się nie przepełnia. Lepszą nazwą może być AttemptToOverflowStack. Zasadniczo oznacza to, że ostatnia próba dostosowania ramki stosu kończy się błędem, ponieważ na stosie nie ma wystarczającej ilości wolnego miejsca. W stosie może pozostać dużo miejsca, ale za mało miejsca. Zatem każda operacja zależałaby od powodzenia wywołania (zazwyczaj wywołania metody), nigdy nie zostanie wykonana i pozostaje tylko program, aby poradzić sobie z tym faktem. Co oznacza, że tak naprawdę nie różni się od innych wyjątków. W rzeczywistości możesz złapać wyjątek w funkcji, która wykonuje wywołanie.
źródło
Jak już odpowiedział , że jest to możliwe do wykonania kodu, a w szczególności do wywoływania funkcji po złapanie
StackOverflowError
ponieważ normalny wyjątek procedury JVM obsługi odwija stos międzythrow
acatch
punkty, uwalniając stosu przestrzeń do użycia. Twój eksperyment potwierdza, że tak jest.Nie jest to jednak to samo, co stwierdzenie, że generalnie można odzyskać od pliku
StackOverflowError
.A
StackOverflowError
IS-AVirtualMachineError
, czyli IS-ANError
. Jak zauważyłeś, Java zapewnia niejasne porady dotycząceError
:a ty, rozsądnie, dochodzisz do wniosku, że powinno to brzmieć jak złapanie,
Error
może być w porządku w niektórych okolicznościach. Zwróć uwagę, że wykonanie jednego eksperymentu nie dowodzi, że ogólnie rzecz biorąc, coś jest bezpieczne. Mogą to zrobić tylko reguły języka Java i specyfikacje używanych klas. AVirtualMachineError
jest specjalną klasą wyjątku, ponieważ specyfikacja języka Java i specyfikacja maszyny wirtualnej języka Java zawierają informacje o semantyce tego wyjątku. W szczególności ten ostatni mówi :...
Podstawowym problemem jest to, że „nie można przewidzieć”, gdzie i kiedy
StackOverflowError
zostanie wyrzucony test. Nie ma gwarancji, gdzie nie zostanie wyrzucony. Nie można na przykład polegać na tym, że zostanie wyrzucony przy wejściu do metody. To może być rzucony w punkcie wewnątrz metody.Ta nieprzewidywalność jest potencjalnie katastrofalna. Ponieważ może zostać wrzucony w ramach metody, może zostać wyrzucony w części przez sekwencję operacji, które klasa uważa za jedną operację „atomową”, pozostawiając obiekt w stanie częściowo zmodyfikowanym, niespójnym. Gdy obiekt znajduje się w niespójnym stanie, każda próba użycia tego obiektu może skutkować błędnym zachowaniem. We wszystkich praktycznych przypadkach nie możesz wiedzieć, który obiekt jest w niespójnym stanie, więc musisz założyć, że żadne obiekty nie są godne zaufania. Każda operacja odzyskiwania lub próba kontynuowania po przechwyceniu wyjątku może zatem mieć błędne zachowanie. Dlatego jedyną bezpieczną rzeczą jest nie złapanie pliku
StackOverflowError
, ale raczej zezwolenie na zakończenie działania programu. (W praktyce możesz próbować rejestrować błędy, aby pomóc w rozwiązywaniu problemów, ale nie możesz polegać na tym, że rejestrowanie działa poprawnie). Oznacza to, że nie można niezawodnie odzyskać danych z plikuStackOverflowError
.źródło