Przechwytywanie java.lang.OutOfMemoryError?

102

Dokumentacja dla java.lang.Errormó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.Errorpodklasa 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 OutOfMemoryErrorzostanie ponownie rzucony.

Więc moje pytanie brzmi:

  1. Czy są jakieś rzeczywiste scenariusze, w których złapanie java.lang.OutOfMemoryErrormoże być dobrym pomysłem?
  2. 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)?
Denis Bazhenov
źródło
Na Twoje pierwsze pytanie dodam, że wyłapię OutOfMemoryError, aby (przynajmniej spróbować) powiadomić użytkownika o problemie. Wcześniej błąd nie został przechwycony przez klauzulę catch (wyjątek e) i użytkownik nie otrzymał żadnej informacji zwrotnej.
Josep Rodríguez López
1
Istnieją konkretne przypadki, np. Przydzielanie gigantycznej tablicy, w których można złapać błąd OOM wokół tej operacji i dość dobrze odzyskać. Ale umieszczenie próby / złapania wokół dużej porcji kodu i próba czystego odzyskania i kontynuowania jest prawdopodobnie złym pomysłem.
Hot Licks

Odpowiedzi:

86

Zgadzam się i nie zgadzam się z większością odpowiedzi tutaj.

Istnieje wiele scenariuszy, w których możesz chcieć złapać OutOfMemoryErrori z mojego doświadczenia (w Windows i Solaris JVM), tylko bardzo rzadko jest to OutOfMemoryErrorkoniec JVM.

Jest tylko jeden dobry powód, aby złapać, OutOfMemoryErrora 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, OutOfMemoryErrorwystępuje z powodu alokacji pamięci blokowej, która nie może być zadowolona z pozostałych zasobów sterty.

Po Errorwyrzuceniu 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, OutOfMemoryErrorktóra nie oznacza, że ​​w JVM zabrakło pamięci w bloku catch:

private static final int MEGABYTE = (1024*1024);
public static void runOutOfMemory() {
    MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
    for (int i=1; i <= 100; i++) {
        try {
            byte[] bytes = new byte[MEGABYTE*500];
        } catch (Exception e) {
            e.printStackTrace();
        } catch (OutOfMemoryError e) {
            MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
            long maxMemory = heapUsage.getMax() / MEGABYTE;
            long usedMemory = heapUsage.getUsed() / MEGABYTE;
            System.out.println(i+ " : Memory Use :" + usedMemory + "M/" + maxMemory + "M");
        }
    }
}

Wyjście tego kodu:

1 : Memory Use :0M/247M
..
..
..
98 : Memory Use :0M/247M
99 : Memory Use :0M/247M
100 : Memory Use :0M/247M

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 go Errorma 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.

Chris
źródło
Zgadzam się, opublikuję mój eksperyment na nowej odpowiedzi.
Mister Smith
4
„nigdy nie można mieć 100% pewności, że JVM jest w stanie naprawialnym”: ponieważ OutOfMemoryErrormó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/…
Raedwald
W OpenJdk1.7.0_40 nie pojawia się żaden błąd ani wyjątek, gdy uruchamiam ten kod. Nawet zmieniłem MEGABYTE na GIGABYTE (1024 * 1024 * 1024). Czy to dlatego, że optymalizator usuwa zmienną „bajt [] bajtów”, ponieważ nie jest ona używana w pozostałej części kodu?
RoboAlex
„Istnieje wiele scenariuszy, w których możesz chcieć złapać OutOfMemoryError” w porównaniu z „Jest tylko jeden dobry powód, aby złapać OutOfMemoryError” . Uzupełnić swój umysł!!!
Stephen C
3
Scenariusz ze świata rzeczywistego, w którym MOŻESZ chcieć złapać błąd OutOfMemory: gdy jest on spowodowany próbą przydzielenia tablicy zawierającej więcej niż 2G elementów. Nazwa błędu jest w tym przypadku trochę myląca, ale nadal jest to OOM.
Charles Roth
31

Państwo może odzyskać od niego:

package com.stackoverflow.q2679330;

public class Test {

    public static void main(String... args) {
        int size = Integer.MAX_VALUE;
        int factor = 10;

        while (true) {
            try {
                System.out.println("Trying to allocate " + size + " bytes");
                byte[] bytes = new byte[size];
                System.out.println("Succeed!");
                break;
            } catch (OutOfMemoryError e) {
                System.out.println("OOME .. Trying again with 10x less");
                size /= factor;
            }
        }
    }

}

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ń:

1: czy są jakieś prawdziwe scenariusze słowne, gdy przechwytywanie java.lang.OutOfMemoryError może być dobrym pomysłem?

Nikt nie przychodzi na myśl.

2: jeśli przechwytujemy błąd java.lang.OutOfMemoryError, jak możemy się upewnić, że program obsługi catch nie przydziela samodzielnie żadnej pamięci (żadnych narzędzi lub najlepszych praktyk)?

Zależy od tego, co spowodowało OOME. Jeśli zostało zadeklarowane poza tryblokiem i stało się to krok po kroku, Twoje szanse są niewielkie. Ty może chcesz zarezerwować trochę miejsca pamięci uprzednio:

private static byte[] reserve = new byte[1024 * 1024]; // Reserves 1MB.

a następnie ustaw na zero podczas OOME:

} catch (OutOfMemoryException e) {
     reserve = new byte[0];
     // Ha! 1MB free!
}

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.

BalusC
źródło
1
Nawet zarezerwowanie miejsca nie gwarantuje jednak działającego rozwiązania. Tę przestrzeń może też
zająć
@Wolph: W takim razie daj JVM więcej pamięci! O_o To wszystko rzeczywiście nie ma sensu;)
BalusC
2
Pierwszy fragment działa, ponieważ obiekt, który wywołał błąd, jest pojedynczym obiektem BIG (tablicą). Po osiągnięciu klauzuli catch została ona zebrana przez żądną pamięci maszynę JVM. Jeśli używałeś tego samego obiektu poza blokiem try, w innym wątku lub w tym samym catch, JVM go nie zbiera, co uniemożliwia utworzenie nowego pojedynczego obiektu dowolnego rodzaju. na przykład drugi fragment może nie działać.
Mister Smith
3
dlaczego po prostu nie ustawić go na null?
Pacerier,
1
@MisterSmith Twój komentarz nie ma sensu. Duży obiekt nie istnieje. W pierwszej kolejności nie został przydzielony: wywołał OOM, więc z pewnością nie potrzebuje GC.
Markiz Lorne
16

Ogólnie złym pomysłem jest próba wyłapania i wyzdrowienia z OOM.

  1. 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.

  2. 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:

  • co konkretnie spowodowało OOME,
  • co aplikacja robiła w tym czasie i że można po prostu odrzucić te obliczenia, i
  • że (w przybliżeniu) jednoczesne OOME nie mogło wystąpić w innym wątku.

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.)

Stephen C.
źródło
Tak, zgadzam się z tobą. Ogólnie to zły pomysł. Ale dlaczego w takim razie mam możliwość go złapać? :)
Denis Bazhenov
@dotsid - 1), ponieważ są przypadki, w których powinieneś go złapać, i 2) ponieważ uniemożliwienie przechwycenia OOM miałoby negatywny wpływ na język i / lub inne części środowiska wykonawczego Java.
Stephen C
1
Mówisz: „ponieważ są przypadki, w których powinieneś to złapać”. Więc to była część mojego pierwotnego pytania. Jakie są przypadki, gdy chcesz złapać OOME?
Denis Bazhenov
1
@dotsid - zobacz moją zredagowaną odpowiedź. Jedynym przypadkiem, o którym mogę pomyśleć, aby wyłapać OOM, jest sytuacja, w której musisz to zrobić, aby wymusić zamknięcie aplikacji wielowątkowej w przypadku OOM. Może chcesz to zrobić dla wszystkich podtypów domeny Error.
Stephen C
1
Nie chodzi tylko o złapanie OOME. Musisz także odzyskać od nich. Jak odzyskuje się wątek, jeśli miał (powiedzmy) powiadomić inny wątek ... ale otrzymał OOME? Jasne, JVM nie umrze. Jednak aplikacja prawdopodobnie przestanie działać, ponieważ wątki utknęły w oczekiwaniu na powiadomienia z wątków, które zostały ponownie uruchomione z powodu przechwycenia OOME.
Stephen C
14

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ć OutOfMemoryErrori 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).

Michael Kuhn
źródło
Więc masz taki kod byte[] bytes = new byte[length]? Dlaczego nie sprawdzić sizewcześniej?
Raedwald
1
Ponieważ to samo sizebędzie dobrze z większą ilością pamięci. Korzystam z wyjątku, ponieważ w większości przypadków wszystko będzie dobrze.
Michael Kuhn
10

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.

Yishai
źródło
8

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:

BitmapFactory.Options bitmapOptions = new BitmapFactory.Options(); 
bitmapOptions.inSampleSize = 1;
boolean imageSet = false;
while (!imageSet) {
  try {
    image = BitmapFactory.decodeFile(filePath, bitmapOptions);
    imageView.setImageBitmap(image); 
    imageSet = true;
  }
  catch (OutOfMemoryError e) {
    bitmapOptions.inSampleSize *= 2;
  }
}

W ten sposób udaje mi się zapewnić mocniejsze i słabsze urządzenia zgodnie z ich potrzebami i oczekiwaniami użytkowników.

Maria
źródło
1
Inną opcją byłoby obliczenie, jak duże mapy bitowe można obsłużyć, zamiast próbować i kończyć się niepowodzeniem. „Wyjątki powinny być stosowane w wyjątkowych przypadkach” - chyba ktoś powiedział. Ale powiem, że twoje rozwiązanie wydaje się najłatwiejszym wyjściem, być może nie najlepszym, ale prawdopodobnie najłatwiejszym.
jontejj,
To zależy od kodeka. Wyobraź sobie, że plik bmp o wielkości 10 MB prawdopodobnie daje tylko nieco więcej niż 10 MB sterty, podczas gdy plik JPEG o wielkości 10 MB „eksploduje”. To samo w moim przypadku, gdy chcę przeanalizować XML, który może się znacznie różnić w zależności od złożoności treści
Daniel Alder
5

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:

-XX:+PrintCommandLineFlags
-XX:+PrintConcurrentLocks
-XX:+PrintClassHistogram

Zobacz pełną listę tutaj

Rob Heiser
źródło
To jest nawet gorsze niż myślisz. Ponieważ an OutOfMemeoryError może zostać wyrzucony w dowolnym punkcie programu (nie tylko z newinstrukcji), twój program będzie w stanie niezdefiniowanym, gdy złapiesz to wyrażenie.
Raedwald
5

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:

boolean isOutOfMemory = false; // flaga używana do raportowania
próbować {
   SomeType largeVar;
   // Główna pętla, która przydziela coraz więcej do largeVar
   // może zakończyć OK lub zgłosić OutOfMemoryError
}
catch (OutOfMemoryError ex) {
   // largeVar jest teraz poza zasięgiem, więc to śmieci
   System.gc (); // wyczyść dane largeVar
   isOutOfMemory = true; // flaga dostępna do użycia
}
// program testuje flagę, aby zgłosić odzyskanie

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.

Tony Simons
źródło
W aplikacji z jednym wątkiem, jeśli nie używasz już niektórych problematycznych nowych obiektów, których utworzenie spowodowało błąd, mogą one zostać zebrane w klauzuli catch. Jeśli jednak maszyna JVM wykryje, że obiekt może być użyty później, nie można go zebrać, a aplikacja wysadza. Zobacz moją odpowiedź w tym wątku.
Mister Smith
4

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:

import java.util.LinkedList;
import java.util.List;

public class OOMErrorTest {             
    public static void main(String[] args) {
        List<Long> ll = new LinkedList<Long>();

        try {
            long l = 0;
            while(true){
                ll.add(new Long(l++));
            }
        } catch(OutOfMemoryError oome){         
            System.out.println("Error catched!!");
        }
        System.out.println("Test finished");
    }  
}

Jednak dodanie tylko jednej linii na haczyku pokaże ci, o czym mówię:

import java.util.LinkedList;
import java.util.List;

public class OOMErrorTest {             
    public static void main(String[] args) {
        List<Long> ll = new LinkedList<Long>();

        try {
            long l = 0;
            while(true){
                ll.add(new Long(l++));
            }
        } catch(OutOfMemoryError oome){         
            System.out.println("Error catched!!");
            System.out.println("size:" +ll.size());
        }
        System.out.println("Test finished");
    }
}

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 lljest 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 w System.out,printlnlinii), 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.

Panie Smith
źródło
Co jeśli zrobię ll=nullwewnątrz bloku zaczepowego?
Naanavanalla
3

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.

cHao
źródło
3

W przypadku pytania 2 już widzę rozwiązanie, które proponuję, autorstwa BalusC.

  1. Czy są jakieś prawdziwe scenariusze słowne, gdy przechwytywanie java.lang.OutOfMemoryError może być dobrym pomysłem?

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.

Jarekczek
źródło
3

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ą.

Jörg Eisfeld
źródło
0
  1. Zależy od tego, jak zdefiniujesz „dobry”. Robimy to w naszej błędnej aplikacji internetowej i działa przez większość czasu (na szczęście teraz OutOfMemorynie 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.
  2. O ile rozumiem, rozwijanie ciężkiego stosu po drodze unieważni tak wiele odniesień, a tym samym zwolni tak dużo pamięci, że nie powinieneś się tym przejmować.

EDYCJA: Proponuję wypróbować. Powiedzmy, napisz program, który rekurencyjnie wywołuje funkcję, która stopniowo przydziela więcej pamięci. Złap OutOfMemoryErrori 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.

doublep
źródło
-1

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.

TofuBeer
źródło
7
Błędnym przekonaniem, które utrwala twój post, jest to, że OOM wskazuje, że JVM nie ma pamięci. Zamiast tego w rzeczywistości wskazuje, że maszyna JVM nie mogła przydzielić całej pamięci, do której została poinstruowana. Oznacza to, że jeśli maszyna JVM ma 10B miejsca i „odświeżysz” obiekt 100B, to się nie powiedzie, ale możesz obrócić i „odświeżyć” obiekt 5B i wszystko będzie dobrze.
Tim Bender,
1
A jeśli potrzebowałbym tylko 5B, dlaczego proszę o 10B? Jeśli dokonujesz alokacji na podstawie prób i błędów, robisz to źle.
TofuBeer
2
Myślę, że Tim miał na myśli, że nadal możesz wykonać jakąś pracę, nawet w sytuacji OutOfMemory. Na przykład może być wystarczająco dużo pamięci, aby otworzyć okno dialogowe.
Stephen Eilert