Dokumentacja dla java.lang.Error
mówi:
Błąd to podklasa Throwable, która wskazuje na poważne problemy, których rozsądna aplikacja nie powinna próbować wychwycić
Ale podobnie jak java.lang.Error
podklasa java.lang.Throwable
, mogę złapać ten rodzaj Throwable.
Rozumiem, dlaczego wyłapywanie tego rodzaju wyjątków nie jest dobrym pomysłem. O ile rozumiem, jeśli zdecydujemy się go złapać, procedura przechwytywania nie powinna sama przydzielać pamięci. W przeciwnym razie OutOfMemoryError
zostanie ponownie rzucony.
Więc moje pytanie brzmi:
- Czy są jakieś rzeczywiste scenariusze, w których złapanie
java.lang.OutOfMemoryError
może być dobrym pomysłem? - Jeśli zdecydujemy się złapać
java.lang.OutOfMemoryError
, w jaki sposób możemy się upewnić, że program obsługi catch nie przydzieli samodzielnie żadnej pamięci (żadnych narzędzi lub najlepszych praktyk)?
java
try-catch
out-of-memory
Denis Bazhenov
źródło
źródło
Odpowiedzi:
Zgadzam się i nie zgadzam się z większością odpowiedzi tutaj.
Istnieje wiele scenariuszy, w których możesz chcieć złapać
OutOfMemoryError
i z mojego doświadczenia (w Windows i Solaris JVM), tylko bardzo rzadko jest toOutOfMemoryError
koniec JVM.Jest tylko jeden dobry powód, aby złapać,
OutOfMemoryError
a mianowicie, aby zamknąć z wdziękiem, czysto zwolnić zasoby i zarejestrować przyczynę niepowodzenia najlepiej, jak możesz (jeśli nadal jest to możliwe).Ogólnie rzecz biorąc,
OutOfMemoryError
występuje z powodu alokacji pamięci blokowej, która nie może być zadowolona z pozostałych zasobów sterty.Po
Error
wyrzuceniu sterta zawiera taką samą ilość przydzielonych obiektów, jak przed nieudaną alokacją, a teraz jest czas na porzucenie odwołań do obiektów czasu wykonywania, aby zwolnić jeszcze więcej pamięci, która może być wymagana do czyszczenia. W takich przypadkach może być nawet możliwe kontynuowanie, ale byłby to zdecydowanie zły pomysł, ponieważ nigdy nie można mieć 100% pewności, że JVM jest w stanie nadającym się do naprawy.Demonstracja,
OutOfMemoryError
która nie oznacza, że w JVM zabrakło pamięci w bloku catch:Wyjście tego kodu:
Jeśli uruchamiam coś krytycznego, zwykle przechwytuję
Error
, loguję do syserr, następnie loguję za pomocą wybranego przeze mnie środowiska rejestrowania, a następnie przystępuję do zwalniania zasobów i zamykania w czysty sposób. Co najgorszego może się wydarzyć? JVM i tak umiera (lub już nie żyje), a złapanie goError
ma przynajmniej szansę na oczyszczenie.Zastrzeżenie polega na tym, że musisz skupić się na wychwytywaniu tego typu błędów tylko w miejscach, w których możliwe jest czyszczenie. Nie zakrywaj
catch(Throwable t) {}
wszędzie ani takich bzdur.źródło
OutOfMemoryError
mógł zostać wyrzucony z punktu, który spowodował, że program stał się niespójny, ponieważ można go wyrzucić w dowolnym momencie. Zobacz stackoverflow.com/questions/8728866/…Państwo może odzyskać od niego:
Ale czy to ma sens? Co jeszcze chciałbyś robić? Dlaczego początkowo miałbyś przydzielić tak dużo pamięci? Czy mniej pamięci też jest OK? Dlaczego i tak już z tego nie korzystasz? A jeśli to niemożliwe, dlaczego po prostu nie dać wirtualnej maszyny Java od samego początku większej ilości pamięci?
Wracając do pytań:
Nikt nie przychodzi na myśl.
Zależy od tego, co spowodowało OOME. Jeśli zostało zadeklarowane poza
try
blokiem i stało się to krok po kroku, Twoje szanse są niewielkie. Ty może chcesz zarezerwować trochę miejsca pamięci uprzednio:a następnie ustaw na zero podczas OOME:
Oczywiście to wszystko nie ma sensu;) Po prostu daj JVM wystarczającą ilość pamięci, zgodnie z wymaganiami aplikacji. W razie potrzeby uruchom profiler.
źródło
null
?Ogólnie złym pomysłem jest próba wyłapania i wyzdrowienia z OOM.
OOME mógł również zostać rzucony w inne wątki, w tym wątki, o których Twoja aplikacja nawet nie wie. Wszelkie takie wątki będą teraz martwe, a wszystko, co czekało na powiadomienie, może utknąć na zawsze. Krótko mówiąc, Twoja aplikacja może zostać śmiertelnie uszkodzona.
Nawet jeśli uda ci się odzyskać, Twoja maszyna JVM może nadal cierpieć z powodu głodzenia sterty, w wyniku czego aplikacja będzie działać fatalnie.
Najlepszą rzeczą do zrobienia z OOME jest pozwolenie JVM umrzeć.
(Przy założeniu, że JVM nie umiera. Na przykład Ooms na serwletu wątku Tomcat nie zabijaj JVM, a to prowadzi do Tomcat wchodząc w stan katatonii, gdzie nie będzie reagować na wszelkie prośby ... nawet nie zwraca się uruchom ponownie.)
EDYTOWAĆ
Nie mówię, że złapanie OOM jest złym pomysłem. Problemy pojawiają się, gdy próbujesz wyjść z OOME, celowo lub przez niedopatrzenie. Za każdym razem, gdy złapiesz OOM (bezpośrednio lub jako podtyp Error lub Throwable), powinieneś albo wyrzucić go ponownie, albo zorganizować zamknięcie aplikacji / JVM.
Poza tym: sugeruje to, że dla maksymalnej niezawodności w obliczu OOM aplikacja powinna użyć Thread.setDefaultUncaughtExceptionHandler (), aby ustawić procedurę obsługi, która spowoduje zamknięcie aplikacji w przypadku OOME, bez względu na wątek, w którym OOME zostanie wyrzucony. Byłbym zainteresowany opiniami na ten temat ...
Jedynym innym scenariuszem jest sytuacja, w której wiesz na pewno, że OOM nie spowodował żadnych dodatkowych szkód; czyli wiesz:
Istnieją aplikacje, w których można poznać te rzeczy, ale w przypadku większości aplikacji nie można mieć pewności, że kontynuacja po OOME jest bezpieczna. Nawet jeśli empirycznie „działa”, kiedy go spróbujesz.
(Problem polega na tym, że wymagany jest formalny dowód, aby wykazać, że konsekwencje „przewidywanych” OOME są bezpieczne i że „nieprzewidziane” OOME nie mogą wystąpić pod kontrolą próby / złapania OOME.)
źródło
Error
.Tak, istnieją scenariusze ze świata rzeczywistego. Oto moje: muszę przetworzyć zestawy danych bardzo wielu elementów w klastrze z ograniczoną pamięcią na węzeł. Dana instancja maszyny JVM przechodzi przez wiele elementów jeden po drugim, ale niektóre z nich są zbyt duże, aby można je było przetworzyć w klastrze: mogę wychwycić
OutOfMemoryError
i zanotować, które elementy są zbyt duże. Później mogę ponownie uruchomić tylko duże elementy na komputerze z większą ilością pamięci RAM.(Ponieważ jest to pojedyncza wielogigabajtowa alokacja tablicy, która się nie udaje, maszyna JVM nadal działa prawidłowo po wykryciu błędu i jest wystarczająco dużo pamięci, aby przetworzyć inne elementy).
źródło
byte[] bytes = new byte[length]
? Dlaczego nie sprawdzićsize
wcześniej?size
będzie dobrze z większą ilością pamięci. Korzystam z wyjątku, ponieważ w większości przypadków wszystko będzie dobrze.Zdecydowanie istnieją scenariusze, w których złapanie OOME ma sens. IDEA przechwytuje je i wyskakuje okno dialogowe, w którym możesz zmienić ustawienia pamięci startowej (a następnie zamyka się, gdy skończysz). Serwer aplikacji może je przechwycić i zgłosić. Kluczem do zrobienia tego jest zrobienie tego na wysokim poziomie w wysyłce, aby mieć rozsądną szansę na uwolnienie dużej ilości zasobów w miejscu, w którym łapiesz wyjątek.
Oprócz powyższego scenariusza IDEA, generalnie przechwytywanie powinno dotyczyć Throwable, a nie tylko OOM, i powinno być wykonywane w kontekście, w którym przynajmniej wątek zostanie wkrótce zakończony.
Oczywiście w większości przypadków pamięć jest głodna, a sytuacji nie da się naprawić, ale są sposoby, w których ma to sens.
źródło
Natknąłem się na to pytanie, ponieważ zastanawiałem się, czy dobrze jest wyłapać OutOfMemoryError w moim przypadku. Odpowiadam tutaj częściowo, aby pokazać kolejny przykład, kiedy wyłapanie tego błędu może mieć sens dla kogoś (tj. Dla mnie), a częściowo, aby dowiedzieć się, czy jest to naprawdę dobry pomysł w moim przypadku (skoro jestem młodszym programistą uber, nigdy nie mogę bądź zbyt pewny co do pojedynczego wiersza kodu, który napiszę).
W każdym razie pracuję nad aplikacją na Androida, którą można uruchomić na różnych urządzeniach z różnymi rozmiarami pamięci. Niebezpieczna część polega na dekodowaniu mapy bitowej z pliku i wyświetleniu jej w instancji ImageView. Nie chcę ograniczać mocniejszych urządzeń pod względem rozmiaru dekodowanej mapy bitowej, ani nie mogę być pewien, że aplikacja nie będzie działać na jakimś starożytnym urządzeniu, z którym nigdy nie spotkałem się przy bardzo małej ilości pamięci. Dlatego robię to:
W ten sposób udaje mi się zapewnić mocniejsze i słabsze urządzenia zgodnie z ich potrzebami i oczekiwaniami użytkowników.
źródło
Tak, prawdziwe pytanie brzmi: „co zamierzasz zrobić w module obsługi wyjątków?” W przypadku prawie wszystkiego pożytecznego przydzielisz więcej pamięci. Jeśli chcesz wykonać pewne prace diagnostyczne, gdy wystąpi błąd OutOfMemoryError, możesz użyć
-XX:OnOutOfMemoryError=<cmd>
zaczepu dostarczonego przez maszynę wirtualną HotSpot. Wykona twoje polecenie (polecenia), gdy wystąpi OutOfMemoryError i możesz zrobić coś pożytecznego poza stertą Javy. Naprawdę chcesz przede wszystkim zapobiec wyczerpaniu pamięci aplikacji, więc pierwszym krokiem jest ustalenie, dlaczego tak się dzieje. Następnie można odpowiednio zwiększyć rozmiar sterty MaxPermSize. Oto kilka innych przydatnych haków HotSpot:Zobacz pełną listę tutaj
źródło
OutOfMemeoryError
może zostać wyrzucony w dowolnym punkcie programu (nie tylko znew
instrukcji), twój program będzie w stanie niezdefiniowanym, gdy złapiesz to wyrażenie.Mam aplikację, która musi naprawić błędy OutOfMemoryError i w programach jednowątkowych zawsze działa, ale czasami nie działa w programach wielowątkowych. Aplikacja jest zautomatyzowanym narzędziem do testowania języka Java, które wykonuje wygenerowane sekwencje testów do maksymalnej możliwej głębokości na klasach testowych. Teraz interfejs użytkownika musi być stabilny, ale silnik testowy może zabraknąć pamięci podczas powiększania drzewa przypadków testowych. Obsługuję to za pomocą następującego idiomu kodu w silniku testowym:
Działa to za każdym razem w aplikacjach jednowątkowych. Ale ostatnio umieściłem mój silnik testowy w osobnym wątku roboczym z interfejsu użytkownika. Teraz brak pamięci może wystąpić dowolnie w dowolnym wątku i nie jest dla mnie jasne, jak to złapać.
Na przykład wystąpił OOME, gdy klatki animowanego GIF-a w moim interfejsie użytkownika były przełączane przez zastrzeżony wątek, który jest tworzony za kulisami przez klasę Swing, która jest poza moją kontrolą. Myślałem, że przydzieliłem z góry wszystkie potrzebne zasoby, ale wyraźnie animator przydziela pamięć za każdym razem, gdy pobiera następny obraz. Jeśli ktoś ma pomysł, jak radzić sobie z OOME podniesionymi w jakimkolwiek wątku, z chęcią go wysłucham.
źródło
OOME może zostać przechwycone, ale będzie generalnie bezużyteczne, w zależności od tego, czy JVM jest w stanie zebrać niektóre obiekty po osiągnięciu pułapki i ile pamięci sterty pozostało do tego czasu.
Przykład: w mojej JVM ten program działa do końca:
Jednak dodanie tylko jednej linii na haczyku pokaże ci, o czym mówię:
Pierwszy program działa dobrze, ponieważ po osiągnięciu złapania JVM wykrywa, że lista nie będzie już używana (to wykrycie może być również optymalizacją wykonaną w czasie kompilacji). Więc kiedy dochodzimy do instrukcji print, pamięć sterty została prawie całkowicie zwolniona, więc mamy teraz szeroki margines manewru, aby kontynuować. To jest najlepszy przypadek.
Jeśli jednak kod jest ułożony tak, jak lista
ll
jest używana po przechwyceniu OOME, JVM nie może go zebrać. Dzieje się to w drugim fragmencie. OOME, wyzwalane przez nową kreację Long, zostaje przechwycone, ale wkrótce tworzymy nowy obiekt (ciąg wSystem.out,println
linii), a sterta jest prawie pełna, więc wyrzucany jest nowy OOME. To jest najgorszy scenariusz: próbowaliśmy stworzyć nowy obiekt, nie udało nam się, złapaliśmy OOME, tak, ale teraz pierwsza instrukcja wymagająca nowej pamięci sterty (np .: utworzenie nowego obiektu) wyrzuci nowy OOME. Pomyśl o tym, co jeszcze możemy zrobić w tym momencie z tak małą ilością pamięci? Prawdopodobnie właśnie wychodzę. Stąd bezużyteczne.Wśród powodów, dla których JVM nie gromadzi zasobów, jeden jest naprawdę przerażający: współdzielony zasób z innymi wątkami również go używającymi. Każdy, kto ma mózg, może zobaczyć, jak niebezpieczne może być złapanie OOME, jeśli zostanie włożone do jakiejś nieeksperymentalnej aplikacji.
Używam 32-bitowej maszyny JVM systemu Windows x86 (JRE6). Domyślna pamięć dla każdej aplikacji Java to 64 MB.
źródło
ll=null
wewnątrz bloku zaczepowego?Jedynym powodem, dla którego mogę wymyślić, dlaczego wyłapywanie błędów OOM może być to, że masz masowe struktury danych, których już nie używasz, i możesz ustawić na zero i zwolnić trochę pamięci. Ale (1) oznacza to, że marnujesz pamięć i powinieneś naprawić swój kod, zamiast po prostu kuleć po OOME, i (2) nawet gdybyś go złapał, co byś zrobił? OOM może się zdarzyć w dowolnym momencie, potencjalnie pozostawiając wszystko w połowie zrobione.
źródło
W przypadku pytania 2 już widzę rozwiązanie, które proponuję, autorstwa BalusC.
Myślę, że właśnie trafiłem na dobry przykład. Kiedy aplikacja awt wysyła komunikaty, nieobejrzany błąd OutOfMemoryError jest wyświetlany na stderr i przetwarzanie bieżącego komunikatu jest zatrzymywane. Ale aplikacja nadal działa! Użytkownik może nadal wydawać inne polecenia, nieświadomy poważnych problemów, które mają miejsce za sceną. Zwłaszcza, gdy nie może lub nie przestrzega błędu standardowego. Więc wychwycenie wyjątku Oom i zapewnienie (lub przynajmniej zasugerowanie) ponownego uruchomienia aplikacji jest czymś pożądanym.
źródło
Mam tylko scenariusz, w którym wyłapywanie OutOfMemoryError wydaje się mieć sens i działa.
Scenariusz: w aplikacji na Androida chcę wyświetlać wiele bitmap w najwyższej możliwej rozdzielczości i mieć możliwość płynnego powiększania ich.
Ze względu na płynne powiększanie chcę mieć mapy bitowe w pamięci. Jednak Android ma ograniczenia pamięci, które są zależne od urządzenia i które są trudne do kontrolowania.
W tej sytuacji może wystąpić błąd OutOfMemoryError podczas odczytu mapy bitowej. Tutaj pomaga, jeśli go złapię, a następnie kontynuuję z niższą rozdzielczością.
źródło
OutOfMemory
nie dzieje się tak z powodu niepowiązanej poprawki). Jednak nawet jeśli go złapiesz, nadal mógł zepsuć jakiś ważny kod: jeśli masz kilka wątków, alokacja pamięci może się nie powieść w każdym z nich. Tak więc, w zależności od aplikacji, nadal istnieje 10--90% szans na nieodwracalne uszkodzenie.EDYCJA: Proponuję wypróbować. Powiedzmy, napisz program, który rekurencyjnie wywołuje funkcję, która stopniowo przydziela więcej pamięci. Złap
OutOfMemoryError
i zobacz, czy możesz sensownie kontynuować od tego momentu. Z mojego doświadczenia wynika, że będzie to możliwe, chociaż w moim przypadku stało się to na serwerze WebLogic, więc mogła być w to zaangażowana czarna magia.źródło
Możesz przechwycić wszystko w Throwable, ogólnie rzecz biorąc, powinieneś przechwytywać tylko podklasy Exception z wyłączeniem RuntimeException (chociaż duża część programistów przechwytuje również RuntimeException ... ale nigdy nie było to zamiarem projektantów języka).
Gdybyś miał złapać OutOfMemoryError, co byś zrobił? Maszynie wirtualnej brakuje pamięci, w zasadzie wszystko, co możesz zrobić, to wyjść. Prawdopodobnie nie możesz nawet otworzyć okna dialogowego, aby powiedzieć im, że brakuje Ci pamięci, ponieważ zajęłoby to pamięć :-)
Maszyna wirtualna generuje błąd OutOfMemoryError, gdy naprawdę brakuje jej pamięci (w rzeczywistości wszystkie błędy powinny wskazywać na sytuacje, których nie można naprawić) i naprawdę nie powinno być nic, co można zrobić, aby sobie z tym poradzić.
Musisz tylko dowiedzieć się, dlaczego brakuje Ci pamięci (użyj profilera, takiego jak ten w NetBeans) i upewnij się, że nie masz wycieków pamięci. Jeśli nie masz wycieków pamięci, zwiększ pamięć przydzieloną maszynie wirtualnej.
źródło