Dlaczego można odzyskać dane ze StackOverflowError?

100

Jestem zaskoczony, jak można kontynuować wykonywanie nawet po StackOverflowErrorwystąpieniu a w Javie.

Wiem, że StackOverflowErrorjest 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?

user3370796
źródło
57
Cały czas popełniam błędy na StackOverflow. Nie powstrzymuje mnie to jednak przed powrotem.
10
Hej, stary ... Słyszałem, że lubisz przepełnienie stosu, więc umieściliśmy przepełnienie stosu w Twoim stackoverflow.com!
Pierre Henry
Ponieważ nowoczesne architektury używają wskaźników ramki, aby ułatwić rozwijanie stosów, nawet częściowych. Tak długo, jak kod + kontekst do tego nie muszą być przydzielane dynamicznie poza stosem, nie powinno być problemu.
RBarryYoung

Odpowiedzi:

119

Gdy stos przepełnia się i StackOverflowErrorzostaje wyrzucony, zwykła obsługa wyjątków rozwija stos. Rozwinięcie stosu oznacza:

  • przerwać wykonywanie aktualnie aktywnej funkcji
  • usuń jego ramkę stosu, przejdź do funkcji wywołującej
  • przerwać wykonywanie wywołującego
  • usuń jego ramkę stosu, przejdź do funkcji wywołującej
  • i tak dalej...

... 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ące fooramek stosu, które wypełniły stos, zostały rozwinięte i większość stosu może być ponownie wykorzystana.


źródło
1
@fge Zapraszam do edycji, rozważałem przerwanie akapitu, ale nie mogłem znaleźć miejsca, w którym wyglądałby dobrze.
1
Przydałoby się
1
Chodzi o to, że najbardziej wewnętrzna strona fookoń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ć potomek trybloku, 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ć.
Simon Richter,
2
@delnan, myślę, że odpowiedź jest niekompletna bez wchodzenia w szczegóły, dlaczego jest to zły pomysł. Różnica w stosunku do jawnie zgłoszonego wyjątku polega na tym, Errorże nie można przewidzieć s, nawet podczas pisania kodu bezpiecznego dla wyjątków.
Simon Richter,
1
@SimonRichter Nie, pytanie jest dość konkretne. To nie o obchodzenie Errors. OP pyta tylko o StackOverflowErrori 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.
Bakuriu
23

Gdy zostanie zgłoszony StackOverflowError, stos jest pełny. Jednak gdy zostanie złapany , wszystkie te foowywołania zostały usunięte ze stosu. barmoże działać normalnie, ponieważ stos nie jest już przepełniony foos. (Zauważ, że nie sądzę, że JLS gwarantuje, że możesz odzyskać od przepełnienia stosu w ten sposób.

user2357112 obsługuje Monikę
źródło
12

Gdy wystąpi StackOverFlow, JVM przejdzie do zaczepu, uwalniając stos.

W Twoim przykładzie usuwa wszystkie skumulowane foo.

Nicolas Defranoux
źródło
8

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.

jmoreno
źródło
1
Po prostu uważaj, jeśli to zrobisz, ponieważ twoja obsługa wyjątków nie wymaga więcej miejsca na stosie niż jest dostępne!
Vince
2

Jak już odpowiedział , że jest to możliwe do wykonania kodu, a w szczególności do wywoływania funkcji po złapanie StackOverflowErrorponieważ normalny wyjątek procedury JVM obsługi odwija stos między throwa catchpunkty, 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 StackOverflowErrorIS-A VirtualMachineError, czyli IS-AN Error. Jak zauważyłeś, Java zapewnia niejasne porady dotyczące Error:

wskazuje na poważne problemy, których rozsądna aplikacja nie powinna próbować wychwycić

a ty, rozsądnie, dochodzisz do wniosku, że powinno to brzmieć jak złapanie, Errormoż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. A VirtualMachineErrorjest 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 :

Implementacja wirtualnej maszyny języka Java wyrzuca obiekt będący instancją podklasy klasy, VirtualMethodErrorgdy błąd wewnętrzny lub ograniczenie zasobów uniemożliwia implementację semantyki opisanej w tym rozdziale. Ta specyfikacja nie pozwala przewidzieć, gdzie mogą wystąpić błędy wewnętrzne lub ograniczenia zasobów, i nie określa dokładnie, kiedy można je zgłosić. W związku z tym każda z VirtualMethodErrorpodklas zdefiniowanych poniżej może zostać wyrzucona w dowolnym momencie podczas działania wirtualnej maszyny języka Java:

...

  • StackOverflowError: W implementacji wirtualnej maszyny języka Java zabrakło miejsca na stosie dla wątku, zazwyczaj dlatego, że wątek wykonuje nieograniczoną liczbę rekurencyjnych wywołań w wyniku błędu w wykonywanym programie.

Podstawowym problemem jest to, że „nie można przewidzieć”, gdzie i kiedy StackOverflowErrorzostanie 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 plikuStackOverflowError, 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 .

Raedwald
źródło