Java.lang.OutOfMemoryError abfangen?

100

Dokumentation für java.lang.Errorsagt:

Ein Fehler ist eine Unterklasse von Throwable, die auf schwerwiegende Probleme hinweist, die eine vernünftige Anwendung nicht abfangen sollte

Aber wie java.lang.Errores eine Unterklasse von ist java.lang.Throwable, kann ich diese Art von Throwable fangen.

Ich verstehe, warum es keine gute Idee ist, diese Art von Ausnahme zu erwischen. Soweit ich weiß, sollte der Catch-Handler keinen Speicher selbst zuweisen, wenn wir uns dazu entschließen, ihn abzufangen. Sonst OutOfMemoryErrorwird wieder geworfen.

Meine Frage lautet also:

  1. Gibt es reale Szenarien, in denen das Fangen java.lang.OutOfMemoryErroreine gute Idee sein könnte?
  2. java.lang.OutOfMemoryErrorWie können wir sicherstellen, dass der Catch-Handler keinen eigenen Speicher zuweist (Tools oder Best Practices), wenn wir uns für den Fang entscheiden ?
Denis Bazhenov
quelle
Bei Ihrer ersten Frage füge ich hinzu, dass ich den OutOfMemoryError abfangen werde, um den Benutzer über das Problem zu informieren (zumindest zu versuchen). Bisher wurde der Fehler nicht von der catch-Klausel (Ausnahme e) erfasst, und dem Benutzer wurde kein Feedback angezeigt.
Josep Rodríguez López
1
Es gibt spezielle Fälle, z. B. das Zuweisen eines gigantischen Arrays, in denen der OOM-Fehler um diesen Vorgang herum abgefangen und relativ gut wiederhergestellt werden kann. Es ist jedoch wahrscheinlich eine schlechte Idee, den Versuch / Fang um einen großen Code-Blob zu platzieren und zu versuchen, sauber wiederherzustellen und fortzufahren.
Hot Licks

Antworten:

84

Ich stimme den meisten Antworten hier zu und stimme ihnen nicht zu.

Es gibt eine Reihe von Szenarien, in denen Sie eine OutOfMemoryErrorund nach meiner Erfahrung (unter Windows- und Solaris-JVMs) nur sehr selten OutOfMemoryErrorden Todesstoß für eine JVM einfangen möchten .

Es gibt nur einen guten Grund, einen zu fangen, OutOfMemoryErrorund zwar, ihn ordnungsgemäß zu schließen, Ressourcen sauber freizugeben und den Grund für den Fehler so gut wie möglich zu protokollieren (sofern dies noch möglich ist).

Im Allgemeinen OutOfMemoryErrortritt dies aufgrund einer Blockspeicherzuordnung auf, die mit den verbleibenden Ressourcen des Heaps nicht zufrieden sein kann.

Wenn der Errorgeworfen wird, enthält der Heap die gleiche Menge zugeordneter Objekte wie vor der nicht erfolgreichen Zuweisung. Jetzt ist es an der Zeit, Verweise auf Laufzeitobjekte zu löschen, um noch mehr Speicher freizugeben, der möglicherweise für die Bereinigung erforderlich ist. In diesen Fällen kann es sogar möglich sein, fortzufahren, aber das wäre definitiv eine schlechte Idee, da Sie niemals 100% sicher sein können, dass sich die JVM in einem reparierbaren Zustand befindet.

Demonstration, OutOfMemoryErrordie nicht bedeutet, dass die JVM im catch-Block nicht genügend Speicher hat:

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");
        }
    }
}

Ausgabe dieses Codes:

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

Wenn etwas Kritisches ausgeführt wird, fange ich das normalerweise ab Error, protokolliere es bei syserr, protokolliere es dann mit dem Protokollierungsframework Ihrer Wahl, setze Ressourcen frei und schließe auf saubere Weise. Was ist das Schlimmste, was passieren kann? Die JVM stirbt ohnehin (oder ist bereits tot) und durch das Fangen Errorbesteht zumindest die Möglichkeit einer Bereinigung.

Die Einschränkung besteht darin, dass Sie diese Fehler nur an Stellen abfangen müssen, an denen eine Bereinigung möglich ist. Decken Sie nicht catch(Throwable t) {}überall oder Unsinn so.

Chris
quelle
Ich stimme zu, ich werde mein Experiment mit einer neuen Antwort veröffentlichen.
Mister Smith
4
"Sie können niemals 100% sicher sein, dass sich die JVM in einem reparierbaren Zustand befindet": weil die OutOfMemoryErrormöglicherweise von einem Punkt aus ausgelöst wurde, der Ihr Programm in einen inkonsistenten Zustand versetzt hat, weil sie jederzeit ausgelöst werden kann. Siehe stackoverflow.com/questions/8728866/…
Raedwald
In OpenJdk1.7.0_40 wird beim Ausführen dieses Codes kein Fehler oder keine Ausnahme angezeigt. Sogar ich habe das MEGABYTE in GIGABYTE (1024 * 1024 * 1024) geändert. Liegt das daran, dass der Optimierer die Variable 'byte [] bytes' entfernt, da sie im Rest des Codes nicht verwendet wird?
RoboAlex
"Es gibt eine Reihe von Szenarien, in denen Sie möglicherweise einen OutOfMemoryError abfangen möchten" im Vergleich zu "Es gibt nur einen guten Grund, einen OutOfMemoryError abzufangen" . Entscheide dich!!!
Stephen C
3
Reales Szenario, wenn Sie möglicherweise einen OutOfMemory-Fehler abfangen möchten: Wenn dies durch den Versuch verursacht wird, ein Array mit mehr als 2G-Elementen zuzuweisen. Der Fehlername ist in diesem Fall etwas falsch, aber es ist immer noch ein OOM.
Charles Roth
31

Sie können sich davon erholen:

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;
            }
        }
    }

}

Aber macht es Sinn? Was möchten Sie noch tun? Warum würden Sie anfänglich so viel Speicher zuweisen? Ist weniger Speicher auch in Ordnung? Warum nutzt du es nicht schon? Oder wenn dies nicht möglich ist, warum nicht einfach der JVM von Anfang an mehr Speicherplatz geben?

Zurück zu Ihren Fragen:

1: Gibt es echte Wortszenarien, wenn das Abfangen von java.lang.OutOfMemoryError eine gute Idee ist?

Niemand fällt mir ein.

2: Wenn wir java.lang.OutOfMemoryError abfangen, wie können wir dann sicherstellen, dass der catch-Handler selbst keinen Speicher zuweist (keine Tools oder Best Practices)?

Kommt darauf an, was das OOME verursacht hat. Wenn es außerhalb des tryBlocks deklariert ist und Schritt für Schritt passiert ist, sind Ihre Chancen gering. Möglicherweise möchten Sie vorher etwas Speicherplatz reservieren:

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

und setzen Sie es dann während OOME auf Null:

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

Natürlich macht das alles mit allem keinen Sinn;) Geben Sie JVM einfach genügend Speicher, wie es Ihre Anwendung erfordert. Führen Sie bei Bedarf einen Profiler aus.

BalusC
quelle
1
Selbst wenn Platz reserviert wird, ist dies keine Garantie für eine funktionierende Lösung. Dieser Platz könnte auch von einem anderen Thread belegt werden;)
Wolph
@Wolph: Dann gib der JVM mehr Speicher! O_o Alles in allem macht es in der Tat keinen Sinn;)
BalusC
2
Das erste Snippet funktioniert, weil das Objekt, das den Fehler ausgelöst hat, ein einzelnes BIG-Objekt (das Array) ist. Wenn die catch-Klausel erreicht ist, wurde sie von der speicherhungrigen JVM gesammelt. Wenn Sie dasselbe Objekt außerhalb des try-Blocks oder in einem anderen Thread oder im selben catch verwendet haben, sammelt JVM es nicht, sodass es unmöglich ist, ein neues einzelnes Objekt zu erstellen. Das zweite Snippet funktioniert möglicherweise nicht.
Mister Smith
3
warum nicht einfach einstellen null?
Pacerier
1
@MisterSmith Dein Kommentar macht keinen Sinn. Das große Objekt existiert nicht. Es wurde überhaupt nicht zugewiesen: Es hat das OOM ausgelöst, daher benötigt es sicherlich keine GC.
Marquis von Lorne
15

Im Allgemeinen ist es eine schlechte Idee, zu versuchen, einen OOM zu fangen und sich von ihm zu erholen.

  1. Ein OOME könnte auch auf andere Threads geworfen worden sein, einschließlich Threads, von denen Ihre Anwendung nicht einmal weiß. Solche Threads sind jetzt tot, und alles, was auf eine Benachrichtigung wartete, konnte für immer hängen bleiben. Kurz gesagt, Ihre App könnte endgültig defekt sein.

  2. Selbst wenn Sie sich erfolgreich erholen, leidet Ihre JVM möglicherweise immer noch unter Heap-Hunger und Ihre Anwendung wird infolgedessen eine miserable Leistung erbringen.

Das Beste, was man mit einem OOME machen kann, ist, die JVM sterben zu lassen.

(Dies setzt voraus , dass die JVM tut sterben. Zum Beispiel Ooms auf einem Tomcat - Servlet - Thread tötet nicht die JVM, und dies führt zu dem Tomcat geht in einen katatonischen Zustand , in dem er nicht antworten auf alle Anfragen ... nicht einmal Anfragen an Neustart.)

BEARBEITEN

Ich sage nicht, dass es eine schlechte Idee ist, OOM überhaupt zu fangen. Die Probleme treten auf, wenn Sie dann versuchen, sich absichtlich oder durch Versehen von der OOME zu erholen. Wann immer Sie ein OOM abfangen (direkt oder als Subtyp von Error oder Throwable), sollten Sie es entweder erneut werfen oder veranlassen, dass die Anwendung / JVM beendet wird.

Nebenbei: Dies legt nahe, dass eine Anwendung für maximale Robustheit gegenüber OOMs Thread.setDefaultUncaughtExceptionHandler () verwenden sollte , um einen Handler festzulegen, der bewirkt, dass die Anwendung im Falle eines OOME beendet wird, unabhängig davon, auf welchen Thread das OOME geworfen wird. Ich würde mich für Meinungen zu diesem Thema interessieren ...

Das einzige andere Szenario ist, wenn Sie sicher wissen , dass der OOM keinen Kollateralschaden verursacht hat. dh du weißt:

  • was speziell das OOME verursacht hat,
  • was die Anwendung zu der Zeit tat und dass es in Ordnung ist, diese Berechnung einfach zu verwerfen, und
  • dass ein (ungefähr) simultanes OOME nicht in einem anderen Thread aufgetreten sein kann.

Es gibt Anwendungen, bei denen es möglich ist, diese Dinge zu wissen, aber für die meisten Anwendungen können Sie nicht sicher sein, dass die Fortsetzung nach einem OOME sicher ist. Auch wenn es empirisch "funktioniert", wenn Sie es versuchen.

(Das Problem ist, dass ein formeller Nachweis erforderlich ist, um zu zeigen, dass die Konsequenzen "erwarteter" OOMEs sicher sind und dass "unerwartete" OOME nicht unter der Kontrolle eines Try / Catch-OOME auftreten können.)

Stephen C.
quelle
Ja, ich stimme dir zu. Im Allgemeinen ist es eine schlechte Idee. Aber warum habe ich dann die Möglichkeit, es zu fangen? :)
Denis Bazhenov
@dotsid - 1) weil es Fälle gibt, in denen Sie es abfangen sollten, und 2) weil es sich negativ auf die Sprache und / oder andere Teile der Java-Laufzeit auswirken würde, wenn es unmöglich wäre, OOM abzufangen.
Stephen C
1
Sie sagen: "Weil es Fälle gibt, in denen Sie es fangen sollten". Das war also Teil meiner ursprünglichen Frage. Was sind die Fälle, in denen Sie OOME fangen möchten?
Denis Bazhenov
1
@dotsid - siehe meine bearbeitete Antwort. Der einzige Fall, den ich mir vorstellen kann, um OOM abzufangen, ist, wenn Sie dies tun müssen, um zu erzwingen, dass eine Multithread-Anwendung im Falle eines OOM beendet wird. Vielleicht möchten Sie dies für alle Untertypen von tun Error.
Stephen C
1
Es geht nicht nur darum, OOMEs zu fangen. Sie müssen sich auch von ihnen erholen. Wie wird ein Thread wiederhergestellt, wenn er (sagen wir) einen anderen Thread benachrichtigen sollte ... aber ein OOME hat? Sicher, die JVM wird nicht sterben. Es ist jedoch wahrscheinlich, dass die Anwendung nicht mehr funktioniert, da Threads nicht mehr auf Benachrichtigungen von Threads warten, die aufgrund des Abfangens eines OOME neu gestartet wurden.
Stephen C
14

Ja, es gibt reale Szenarien. Hier ist meine: Ich muss Datensätze von sehr vielen Elementen in einem Cluster mit begrenztem Speicher pro Knoten verarbeiten. Eine bestimmte JVM-Instanz durchläuft viele Elemente nacheinander, aber einige Elemente sind zu groß, um im Cluster verarbeitet zu werden: Ich kann das abfangen OutOfMemoryErrorund notieren, welche Elemente zu groß sind. Später kann ich nur die großen Elemente auf einem Computer mit mehr RAM erneut ausführen.

(Da es sich um eine einzelne Multi-Gigabyte-Zuweisung eines Arrays handelt, die fehlschlägt, ist die JVM nach dem Erkennen des Fehlers immer noch in Ordnung und es ist genügend Speicher vorhanden, um die anderen Elemente zu verarbeiten.)

Michael Kuhn
quelle
Sie haben also Code wie byte[] bytes = new byte[length]? Warum nicht einfach sizezu einem früheren Zeitpunkt nachsehen?
Raedwald
1
Weil das gleiche sizemit mehr Speicher gut sein wird. Ich gehe über die Ausnahme, weil in den meisten Fällen alles in Ordnung sein wird.
Michael Kuhn
10

Es gibt definitiv Szenarien, in denen es Sinn macht, ein OOME zu fangen. IDEA fängt sie ab und öffnet ein Dialogfeld, in dem Sie die Einstellungen für den Startspeicher ändern können (und wird dann beendet, wenn Sie fertig sind). Ein Anwendungsserver kann sie abfangen und melden. Der Schlüssel dazu besteht darin, dies beim Versand auf einem hohen Niveau zu tun, damit Sie eine vernünftige Chance haben, an dem Punkt, an dem Sie die Ausnahme abfangen, eine Reihe von Ressourcen freizusetzen.

Neben dem obigen IDEA-Szenario sollte der Fang im Allgemeinen von Throwable sein, nicht nur von OOM, und in einem Kontext, in dem zumindest der Thread in Kürze beendet wird.

Natürlich ist das Gedächtnis meistens ausgehungert und die Situation kann nicht wiederhergestellt werden, aber es gibt Möglichkeiten, wie es Sinn macht.

Yishai
quelle
8

Ich bin auf diese Frage gestoßen, weil ich mich gefragt habe, ob es in meinem Fall eine gute Idee ist, OutOfMemoryError zu fangen. Ich antworte hier teilweise, um ein weiteres Beispiel zu zeigen, wenn das Abfangen dieses Fehlers für jemanden (dh mich) sinnvoll sein kann, und teilweise, um herauszufinden, ob es in meinem Fall tatsächlich eine gute Idee ist (da ich ein überaus junger Entwickler bin, kann ich das nie Seien Sie zu sicher über jede einzelne Codezeile, die ich schreibe.

Wie auch immer, ich arbeite an einer Android-Anwendung, die auf verschiedenen Geräten mit unterschiedlichen Speichergrößen ausgeführt werden kann. Der gefährliche Teil besteht darin, eine Bitmap aus einer Datei zu dekodieren und in einer ImageView-Instanz anzuzeigen. Ich möchte die leistungsstärkeren Geräte nicht auf die Größe der dekodierten Bitmap beschränken und kann auch nicht sicher sein, dass die App nicht auf einem alten Gerät ausgeführt wird, auf das ich noch nie mit sehr wenig Speicher gestoßen bin. Daher mache ich das:

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;
  }
}

Auf diese Weise schaffe ich es, mehr und weniger leistungsstarke Geräte bereitzustellen, die den Bedürfnissen und Erwartungen ihrer Benutzer entsprechen.

Maria
quelle
1
Eine andere Möglichkeit wäre zu berechnen, wie große Bitmaps Sie verarbeiten können, anstatt es zu versuchen und zu scheitern. "Ausnahmen sollten für Ausnahmefälle verwendet werden" - ich denke, jemand sagte. Aber ich werde sagen, Ihre Lösung scheint der einfachste Ausweg zu sein, vielleicht nicht der beste, aber wahrscheinlich der einfachste.
Jontejj
Das hängt vom Codec ab. Stellen Sie sich vor, ein BMP von 10 MB führt wahrscheinlich nur zu etwas mehr als 10 MB Heap, während ein JPEG von 10 MB "explodiert". Gleiches gilt für meinen Fall, in dem ich ein XML analysieren möchte, das je nach Komplexität des Inhalts sehr unterschiedlich sein kann
Daniel Alder,
5

Ja, die eigentliche Frage lautet: "Was werden Sie im Ausnahmebehandler tun?" Für fast alles Nützliche weisen Sie mehr Speicher zu. Wenn Sie beim Auftreten eines OutOfMemoryError Diagnosearbeiten durchführen möchten, können Sie den -XX:OnOutOfMemoryError=<cmd>von der HotSpot-VM bereitgestellten Hook verwenden. Es führt Ihre Befehle aus, wenn ein OutOfMemoryError auftritt, und Sie können etwas Nützliches außerhalb von Javas Heap tun. Sie möchten wirklich verhindern, dass der Anwendung der Speicherplatz ausgeht. Daher ist es der erste Schritt, herauszufinden, warum dies geschieht. Anschließend können Sie die Heap-Größe von MaxPermSize entsprechend erhöhen. Hier sind einige andere nützliche HotSpot-Hooks:

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

Die vollständige Liste finden Sie hier

Rob Heiser
quelle
Es ist noch schlimmer als du denkst. Da OutOfMemeoryError an jedem Punkt in Ihrem Programm (nicht nur aus einer newAnweisung) ein geworfen werden kann, befindet sich Ihr Programm in einem undefinierten Zustand, wenn Sie die Exktion abfangen.
Raedwald
5

Ich habe eine Anwendung, die nach OutOfMemoryError-Fehlern wiederhergestellt werden muss. In Single-Thread-Programmen funktioniert sie immer, in Multithread-Programmen jedoch manchmal nicht. Die Anwendung ist ein automatisiertes Java-Testtool, das generierte Testsequenzen in Testklassen bis zur maximal möglichen Tiefe ausführt. Jetzt muss die Benutzeroberfläche stabil sein, aber der Test-Engine kann der Speicherplatz ausgehen, während der Baum der Testfälle vergrößert wird. Ich behandle dies mit der folgenden Art von Code-Idiom in der Test-Engine:

boolean isOutOfMemory = false; // Flag für die Berichterstellung
Versuchen {
   SomeType largeVar;
   // Hauptschleife, die largeVar immer mehr zuweist
   // kann OK beenden oder OutOfMemoryError auslösen
}}
catch (OutOfMemoryError ex) {
   // largeVar ist jetzt außerhalb des Gültigkeitsbereichs, ebenso wie Müll
   System.gc (); // großeVar-Daten bereinigen
   isOutOfMemory = true; // Flag zur Verwendung verfügbar
}}
// Programm testet Flag, um die Wiederherstellung zu melden

Dies funktioniert jedes Mal in Single-Thread-Anwendungen. Aber ich habe kürzlich meine Test-Engine in einen separaten Worker-Thread von der Benutzeroberfläche gestellt. Jetzt kann der Speichermangel in beiden Threads willkürlich auftreten, und mir ist nicht klar, wie ich ihn abfangen soll.

Ich habe beispielsweise OOME auftreten lassen, während die Frames eines animierten GIF in meiner Benutzeroberfläche von einem proprietären Thread durchlaufen wurden, der hinter den Kulissen von einer Swing-Klasse erstellt wird, die außerhalb meiner Kontrolle liegt. Ich hatte gedacht, ich hätte alle benötigten Ressourcen im Voraus zugewiesen, aber der Animator weist eindeutig jedes Mal Speicher zu, wenn er das nächste Bild abruft. Wenn jemand eine Idee hat, wie man mit OOMEs umgeht, die in einem Thread ausgelöst wurden , würde ich gerne hören.

Tony Simons
quelle
Wenn Sie in einer App mit einem Thread einige der problematischen neuen Objekte, deren Erstellung den Fehler ausgelöst hat, nicht mehr verwenden, werden diese möglicherweise in der catch-Klausel erfasst. Wenn die JVM jedoch feststellt, dass das Objekt später verwendet werden kann, kann es nicht erfasst werden und die App wird in die Luft gesprengt. Siehe meine Antwort in diesem Thread.
Mister Smith
4

OOME kann abgefangen werden, ist jedoch im Allgemeinen nutzlos, je nachdem, ob die JVM in der Lage ist, einige Objekte zu sammeln, wenn der Fang erreicht ist, und wie viel Heapspeicher bis zu diesem Zeitpunkt noch vorhanden ist.

Beispiel: In meiner JVM wird dieses Programm vollständig ausgeführt:

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");
    }  
}

Wenn Sie jedoch nur eine einzelne Zeile in den Fang einfügen, sehen Sie, wovon ich spreche:

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");
    }
}

Das erste Programm läuft einwandfrei, da die JVM bei Erreichen des Catch erkennt, dass die Liste nicht mehr verwendet wird (diese Erkennung kann auch eine Optimierung sein, die zur Kompilierungszeit vorgenommen wird). Wenn wir also die print-Anweisung erreichen, wurde der Heap-Speicher fast vollständig freigegeben, sodass wir jetzt einen großen Handlungsspielraum haben, um fortzufahren. Dies ist der beste Fall.

Wenn der Code jedoch so angeordnet ist, dass die Liste llnach dem Abfangen des OOME verwendet wird, kann die JVM ihn nicht erfassen. Dies geschieht im zweiten Snippet. Das OOME, ausgelöst durch eine neue Long-Erstellung, wird abgefangen, aber bald erstellen wir ein neues Objekt (eine Zeichenfolge in der System.out,printlnZeile), und der Heap ist fast voll, sodass ein neues OOME ausgelöst wird. Dies ist der schlimmste Fall: Wir haben versucht, ein neues Objekt zu erstellen, wir sind fehlgeschlagen, wir haben das OOME abgefangen, ja, aber jetzt löst die erste Anweisung, die neuen Heapspeicher benötigt (z. B. das Erstellen eines neuen Objekts), ein neues OOME aus. Denken Sie darüber nach, was können wir an diesem Punkt noch tun, wenn so wenig Speicher übrig bleibt? Wahrscheinlich nur aufregend. Daher das Nutzlose.

Einer der Gründe, warum die JVM keine Ressourcen sammelt, ist wirklich beängstigend: Eine gemeinsam genutzte Ressource, die auch von anderen Threads verwendet wird. Jeder mit einem Gehirn kann sehen, wie gefährlich das Fangen von OOME sein kann, wenn es in eine nicht experimentelle App jeglicher Art eingefügt wird.

Ich verwende eine Windows x86 32-Bit-JVM (JRE6). Der Standardspeicher für jede Java-App beträgt 64 MB.

Herr Smith
quelle
Was ist, wenn ich ll=nullim Fangblock bin ?
Naanavanalla
3

Der einzige Grund, warum ich mir vorstellen kann, warum das Abfangen von OOM-Fehlern sein könnte, ist, dass Sie einige massive Datenstrukturen haben, die Sie nicht mehr verwenden, und auf null setzen und etwas Speicher freigeben können. Aber (1) das bedeutet, dass Sie Speicher verschwenden, und Sie sollten Ihren Code reparieren, anstatt nur nach einem OOME zu humpeln, und (2) was würden Sie tun, selbst wenn Sie ihn abfangen würden? OOM kann jederzeit auftreten und möglicherweise alles zur Hälfte erledigen.

cHao
quelle
3

Für die Frage 2 sehe ich bereits die Lösung, die ich von BalusC vorschlagen würde.

  1. Gibt es echte Wortszenarien beim Abfangen von java.lang.OutOfMemoryError?

Ich glaube, ich bin gerade auf ein gutes Beispiel gestoßen. Wenn eine awt-Anwendung Nachrichten versendet, wird der nicht abgefangene OutOfMemoryError auf stderr angezeigt und die Verarbeitung der aktuellen Nachricht wird gestoppt. Aber die Anwendung läuft weiter! Der Benutzer kann weiterhin andere Befehle ausgeben, ohne die schwerwiegenden Probleme zu kennen, die hinter den Kulissen auftreten. Besonders wenn er den Standardfehler nicht beobachten kann oder nicht. Daher ist es wünschenswert, eine Ausnahme zu erwischen und einen Neustart der Anwendung bereitzustellen (oder zumindest vorzuschlagen).

Jarekczek
quelle
3

Ich habe nur ein Szenario, in dem das Abfangen eines OutOfMemoryError sinnvoll erscheint und zu funktionieren scheint.

Szenario: In einer Android-App möchte ich mehrere Bitmaps in höchstmöglicher Auflösung anzeigen und sie flüssig zoomen können.

Aufgrund des fließenden Zooms möchte ich die Bitmaps im Speicher haben. Android hat jedoch Speicherbeschränkungen, die geräteabhängig und schwer zu steuern sind.

In dieser Situation kann es beim Lesen der Bitmap zu OutOfMemoryError kommen. Hier hilft es, wenn ich es fange und dann mit niedrigerer Auflösung weitermache.

Jörg Eisfeld
quelle
0
  1. Kommt darauf an, wie du "gut" definierst. Wir machen das in unserer fehlerhaften Webanwendung und es funktioniert die meiste Zeit (zum Glück OutOfMemorypassiert das jetzt nicht aufgrund eines nicht verwandten Fixes). Selbst wenn Sie es abfangen, ist möglicherweise ein wichtiger Code beschädigt: Wenn Sie mehrere Threads haben, kann die Speicherzuweisung in einem dieser Threads fehlschlagen. Abhängig von Ihrer Anwendung besteht also immer noch eine Wahrscheinlichkeit von 10 bis 90%, dass sie irreversibel beschädigt wird.
  2. Soweit ich weiß, macht das Abwickeln von schweren Stapeln auf dem Weg so viele Referenzen ungültig und gibt so viel Speicher frei, dass Sie sich nicht darum kümmern sollten.

EDIT: Ich schlage vor, Sie probieren es aus. Schreiben Sie beispielsweise ein Programm, das rekursiv eine Funktion aufruft, die zunehmend mehr Speicher zuweist. Fangen Sie OutOfMemoryErrorund sehen Sie, ob Sie von diesem Punkt aus sinnvoll weitermachen können. Nach meiner Erfahrung werden Sie in der Lage sein, obwohl dies in meinem Fall unter dem WebLogic-Server geschehen ist, sodass möglicherweise schwarze Magie beteiligt war.

doublep
quelle
-1

Sie können alles unter Throwable abfangen. Im Allgemeinen sollten Sie nur Unterklassen von Exception mit Ausnahme von RuntimeException abfangen (obwohl ein großer Teil der Entwickler auch RuntimeException abfängt ... aber das war nie die Absicht der Sprachdesigner).

Wenn Sie OutOfMemoryError fangen würden, was um alles in der Welt würden Sie tun? Die VM verfügt nicht über genügend Arbeitsspeicher. Grundsätzlich können Sie nur beenden. Sie können wahrscheinlich nicht einmal ein Dialogfeld öffnen, um ihnen mitzuteilen, dass Sie nicht genügend Speicher haben, da dies Speicherplatz beanspruchen würde :-)

Die VM löst einen OutOfMemoryError aus, wenn wirklich nicht genügend Arbeitsspeicher vorhanden ist (tatsächlich sollten alle Fehler auf nicht behebbare Situationen hinweisen), und es sollte wirklich nichts geben, was Sie tun können, um damit umzugehen.

Sie müssen herausfinden, warum Ihnen der Speicher ausgeht (verwenden Sie einen Profiler wie den in NetBeans) und sicherstellen, dass keine Speicherlecks auftreten. Wenn Sie keine Speicherlecks haben, erhöhen Sie den Speicher, den Sie der VM zuweisen.

TofuBeer
quelle
7
Ein Missverständnis, das Ihr Beitrag aufrechterhält, ist, dass OOM anzeigt, dass die JVM nicht über genügend Speicher verfügt. Stattdessen zeigt es tatsächlich an, dass die JVM nicht den gesamten Speicher zuweisen konnte, dem sie angewiesen wurde. Das heißt, wenn die JVM 10B Speicherplatz hat und Sie ein 100B-Objekt "neu" machen, schlägt dies fehl, aber Sie könnten sich umdrehen und ein 5B-Objekt "neu" machen und in Ordnung sein.
Tim Bender
1
Und wenn ich nur 5B brauchte, warum frage ich dann nach 10B? Wenn Sie eine Zuordnung basierend auf Versuch und Irrtum vornehmen, machen Sie es falsch.
TofuBeer
2
Ich denke, Tim meinte, dass Sie auch in einer OutOfMemory-Situation noch einige Arbeiten ausführen können. Möglicherweise ist noch genügend Speicher vorhanden, um beispielsweise einen Dialog zu öffnen.
Stephen Eilert