Czy * kiedykolwiek * jest w porządku złapać StackOverflowError w Javie?

27

Kiedyś myślałem, że tak nie jest, ale wczoraj musiałem to zrobić. Jest to aplikacja korzystająca z Akka (implementacja systemu aktorów dla JVM) do przetwarzania zadań asynchronicznych. Jeden z aktorów wykonuje pewne manipulacje PDF, a ponieważ biblioteka jest błędna, umiera StackOverflowErrorco jakiś czas.

Drugi aspekt polega na tym, że Akka jest skonfigurowane do zamykania całego systemu aktorów, jeśli zostanie wykryty błąd krytyczny JVM (np. StackOverflowError).

Trzecim aspektem jest to, że ten system aktorów jest osadzony w aplikacji internetowej (z powodu WTF, dziedzictwa, powodów), więc gdy system aktorów jest zamknięty, aplikacja internetowa nie. W efekcie StackOverflowErrornasza aplikacja do przetwarzania zadań staje się pustą aplikacją internetową.

Jako szybką poprawkę musiałem złapać StackOverflowErrorrzucanego, aby pula wątków systemu aktorów nie została zerwana. Doprowadziło mnie to do wniosku, że może czasem dobrze jest wyłapać takie błędy, szczególnie w takich kontekstach? Kiedy pula wątków przetwarza dowolne zadania? W przeciwieństwie do OutOfMemoryErrornie mogę sobie wyobrazić, jak a StackOverflowErrormoże pozostawić aplikację w niespójnym stanie. Stos jest usuwany po takim błędzie, więc obliczenia mogą przebiegać normalnie. Ale może brakuje mi czegoś ważnego.

Pamiętajmy też, że przede wszystkim jestem za naprawieniem błędu (tak naprawdę kilka dni temu naprawiłem już SOE w tej samej aplikacji), ale tak naprawdę nie wiem, kiedy to może powstać taka sytuacja.

Dlaczego lepiej byłoby zrestartować proces JVM zamiast łapać StackOverflowError, oznaczyć to zadanie jako nieudane i kontynuować moją działalność?

Czy jest jakiś ważny powód, aby nigdy nie łapać SOE? Z wyjątkiem „najlepszych praktyk”, które są niejasnym terminem, który nic mi nie mówi.

Ionuț G. Stan
źródło
1
inną opcją byłoby zwiększenie przestrzeni stosu dostępnej w JVM
maniak zapadkowy
3
@ratchetfreak: StackOverflowExceptions są zwykle spowodowane nie kończącym się łańcuchem wywołań metod - zwiększenie przestrzeni stosu zwiększyłoby wówczas koszt pamięci nowego wątku bez żadnej korzyści.
jhominal
1
Przynajmniej jedno SOE było uzasadnione, ponieważ dane wejściowe były bardzo duże. Niestety, obsługa go za pomocą rekurencyjnej implementacji (impl. Regex Java) nie była zbyt dobrym pomysłem. W każdym razie, nawet jeśli zagwarantowane jest zakończenie obliczeń, nie wiadomo, czy nowy rozmiar stosu jest wystarczająco duży dla innych obliczeń.
Ionuț G. Stan
2
Czy to nie powinno być przeniesione do Sta ... Och, czekaj ... nieważne. :-)
Blrfl,
Odnośnie twojej błędnej biblioteki. Powinieneś naprawdę migrować tę funkcję manipulacji pdf do własnego procesu, abyś mógł ją zabić.
Esben Skov Pedersen

Odpowiedzi:

44

Zasadniczo, gdyby absolutnie nigdy, nigdy nie można było nic zrobić, i istniała zgoda co do tego, realizatorzy języków nie pozwoliliby na to. Prawie nie ma tak jednoznacznie jednoznacznych maksym. (Na szczęście, ponieważ to utrzymuje nas programiści ludzcy w pracy!)

Wygląda to tak, jakbyś znalazł sytuację, w której złapanie tego błędu jest najlepszą opcją: pozwala aplikacji działać, podczas gdy wszystkie inne alternatywy nie, i to się na końcu liczy. Wszystkie „najlepsze praktyki” to po prostu podsumowanie długich doświadczeń z wieloma przypadkami, które zwykle można zastosować zamiast szczegółowej analizy konkretnego przypadku, aby zaoszczędzić czas; w twoim przypadku wykonałeś już konkretną analizę i uzyskałeś inny wynik. Gratulacje, jesteś zdolny do samodzielnego myślenia!

(To powiedziawszy, z pewnością istnieją sytuacje, w których przepełnienie stosu może spowodować niespójność aplikacji, podobnie jak wyczerpanie pamięci. Wyobraź sobie, że jakiś obiekt jest konstruowany, a następnie inicjowany za pomocą zagnieżdżonych wewnętrznych wywołań metod - jeśli jedno z nich wyrzuci, obiekt może być w stanie, który nie powinien być możliwy, tak jak gdyby alokacja nie powiodła się. Ale to nie znaczy, że twoje rozwiązanie nie może być najlepsze.)

Kilian Foth
źródło
3
Dzięki. Moje wątpliwości zostały nieco wzmocnione, gdy dowiedziałem się, że .NET zrobił StackOverflowExceptionwyjątek, którego nie można złapać. Wiem, że to inna platforma, ale myślałem, że mogli mieć powód. Również twój punkt widzenia w odniesieniu do inicjalizacji obiektu jest natychmiastowy. To prowadzi mnie do myślenia, że ​​powinienem złapać to SOE kilka warstw abstrakcji poniżej, aby nie złapać „złego” SOE.
Ionuț G. Stan
14
+1: najlepsze praktyki powinny zawsze zawierać wyjaśnienie, dlaczego iw jakim kontekście są „najlepsze”, abyś mógł ocenić, czy mają zastosowanie w konkretnym przypadku.
Michael Borgwardt,
situations herepowinno być situations where.
Servy
2

Nie wiem, czy są tutaj jakieś ryzyka specyficzne dla JVM, ale ogólnie wydaje się to całkiem rozsądne.

Na przykład istnieją algorytmy rekurencyjne, takie jak naiwne szybkie sortowanie, które mają log(n)głębokość stosu w typowym przypadku, ale w najgorszym przypadku zmniejszają się do głębokości, nktóra może wysadzić stos.

Najgorszy przypadek jest rzadki i mało prawdopodobne jest, aby powtórzył się po ponownym uruchomieniu sortowania w częściowo posortowanym zestawie, więc sensowne jest wychwycenie wyjątku przepełnienia stosu i ponowne uruchomienie pracy zamiast zapobiegania wystąpieniu błędu lub zabiciu cała aplikacja.

Kornel
źródło