Mir ist bewusst, dass jedes Objekt Heapspeicher benötigt und jedes Grundelement / jede Referenz auf dem Stapel Stapelspeicher benötigt.
Wenn ich versuche, ein Objekt auf dem Heap zu erstellen, und nicht genügend Speicher vorhanden ist, erstellt die JVM einen java.lang.OutOfMemoryError auf dem Heap und wirft ihn mir zu.
Dies bedeutet implizit, dass beim Start von der JVM Speicher reserviert ist.
Was passiert, wenn dieser reservierte Speicher aufgebraucht ist (er würde definitiv aufgebraucht sein, siehe Diskussion unten) und die JVM nicht über genügend Speicher auf dem Heap verfügt, um eine Instanz von java.lang.OutOfMemoryError zu erstellen ?
Hängt es nur? Oder würde er mir einen werfen, null
da es keine Erinnerung an new
eine Instanz von OOM gibt?
try {
Object o = new Object();
// and operations which require memory (well.. that's like everything)
} catch (java.lang.OutOfMemoryError e) {
// JVM had insufficient memory to create an instance of java.lang.OutOfMemoryError to throw to us
// what next? hangs here, stuck forever?
// or would the machine decide to throw us a "null" ? (since it doesn't have memory to throw us anything more useful than a null)
e.printStackTrace(); // e.printStackTrace() requires memory too.. =X
}
==
Warum konnte die JVM nicht genügend Speicher reservieren?
Unabhängig davon, wie viel Speicher reserviert ist, kann dieser Speicher immer noch verbraucht werden, wenn die JVM keine Möglichkeit hat, diesen Speicher "zurückzugewinnen":
try {
Object o = new Object();
} catch (java.lang.OutOfMemoryError e) {
// JVM had 100 units of "spare memory". 1 is used to create this OOM.
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e2) {
// JVM had 99 units of "spare memory". 1 is used to create this OOM.
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e3) {
// JVM had 98 units of "spare memory". 1 is used to create this OOM.
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e4) {
// JVM had 97 units of "spare memory". 1 is used to create this OOM.
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e5) {
// JVM had 96 units of "spare memory". 1 is used to create this OOM.
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e6) {
// JVM had 95 units of "spare memory". 1 is used to create this OOM.
e.printStackTrace();
//........the JVM can't have infinite reserved memory, he's going to run out in the end
}
}
}
}
}
}
Oder genauer:
private void OnOOM(java.lang.OutOfMemoryError e) {
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e2) {
OnOOM(e2);
}
}
quelle
OutOfMemoryException
OutOfMemoryError
abfängt und einen Verweis darauf behält. Es stellt sich heraus, dass das Abfangen von aOutOfMemoryError
nicht so nützlich ist, wie man denkt, da Sie fast nichts über den Status Ihres Programms beim Abfangen garantieren können. Siehe stackoverflow.com/questions/8728866/…Antworten:
Der JVM geht nie wirklich der Speicher aus. Es führt im Voraus eine Speicherberechnung des Heap-Stacks durch.
Die Struktur der JVM, Kapitel 3 , Abschnitt 3.5.2 lautet:
Für Heap Abschnitt 3.5.3.
Daher wird im Voraus eine Berechnung durchgeführt, bevor das Objekt zugewiesen wird.
Was passiert ist, dass die JVM versucht, Speicher für ein Objekt im Speicher zuzuweisen, das als Permanent Generation Region (oder PermSpace) bezeichnet wird. Wenn die Zuweisung fehlschlägt (auch nachdem die JVM den Garbage Collector aufgerufen hat, um freien Speicherplatz zu versuchen und zuzuweisen), wird ein ausgelöst
OutOfMemoryError
. Selbst Ausnahmen erfordern einen Speicherplatz, sodass der Fehler auf unbestimmte Zeit ausgelöst wird.Weiterführende Literatur. ? Darüber hinaus
OutOfMemoryError
kann in unterschiedlicher JVM-Struktur auftreten.quelle
Graham Borland scheint recht zu haben : Zumindest verwendet meine JVM anscheinend OutOfMemoryErrors wieder. Um dies zu testen, habe ich ein einfaches Testprogramm geschrieben:
Wenn Sie es ausführen, wird folgende Ausgabe erstellt:
Übrigens ist die JVM, die ich verwende (unter Ubuntu 10.04), folgende:
Bearbeiten: Ich habe versucht zu sehen, was passieren würde, wenn ich die JVM mit dem folgenden Programm dazu zwingen würde, keinen Speicher mehr zu haben:
Wie sich herausstellt, scheint es für immer zu schleifen. Seltsamerweise funktioniert der Versuch, das Programm mit Ctrl+ zu beenden, Cjedoch nicht, sondern gibt nur die folgende Meldung aus:
Java HotSpot(TM) 64-Bit Server VM warning: Exception java.lang.OutOfMemoryError occurred dispatching signal SIGINT to handler- the VM may need to be forcibly terminated
quelle
n
OOMs vorab zuzuweisen und anschließend eines davon wiederzuverwenden, damit ein OOM immer geworfen werden kann. Die JVM von Sun / Oracle unterstützt überhaupt keine Schwanzrekursion IIRC?n
Frames auf dem Stapel und es schafft und zerstört Framesn+1
für die Ewigkeit, was den Anschein erweckt, endlos zu laufen.Die meisten Laufzeitumgebungen weisen beim Start vorab zu oder reservieren auf andere Weise genügend Speicher, um Speichermangelsituationen zu bewältigen. Ich kann mir vorstellen, dass die meisten vernünftigen JVM-Implementierungen dies tun würden.
quelle
catch
Klausel versucht, mehr Speicher zu verwenden, kann die JVM immer wieder dieselbe OOM-Instanz auslösen.Als ich das letzte Mal in Java arbeitete und einen Debugger verwendete, zeigte der Heap-Inspektor, dass die JVM beim Start eine Instanz von OutOfMemoryError zugewiesen hat. Mit anderen Worten, es weist das Objekt zu, bevor Ihr Programm die Möglichkeit hat, Speicher zu verbrauchen, geschweige denn zu wenig Speicherplatz.
quelle
Aus der JVM-Spezifikation, Kapitel 3.5.2:
Jede Java Virtual Machine muss garantieren, dass sie eine wirft
OutOfMemoryError
.OutOfMemoryError
Dies bedeutet, dass es in der Lage sein muss, eine Instanz von (oder eine im Voraus erstellte) zu erstellen, selbst wenn kein Heap-Speicherplatz mehr vorhanden ist.Obwohl es nicht garantiert werden muss, dass noch genügend Speicher vorhanden ist, um es abzufangen und eine schöne Stapelspur zu drucken ...
Zusatz
Sie haben Code hinzugefügt, um zu zeigen, dass der JVM möglicherweise der Heap-Speicherplatz ausgeht, wenn sie mehr als einen werfen muss
OutOfMemoryError
. Eine solche Implementierung würde jedoch die Anforderung von oben verletzen.Es ist nicht erforderlich, dass die ausgelösten Instanzen von
OutOfMemoryError
eindeutig sind oder bei Bedarf erstellt werden. Eine JVM kannOutOfMemoryError
während des Startvorgangs genau eine Instanz vorbereiten und diese auslösen, wenn der Heap-Speicherplatz knapp wird - was in einer normalen Umgebung einmal der Fall ist. Mit anderen Worten: Die InstanzOutOfMemoryError
, die wir sehen, könnte ein Singleton sein.quelle
Interessante Frage :-). Während die anderen die theoretischen Aspekte gut erklärt haben, habe ich beschlossen, es auszuprobieren. Dies ist unter Oracle JDK 1.6.0_26, Windows 7 64-Bit.
Versuchsaufbau
Ich habe ein einfaches Programm geschrieben, um den Speicher zu erschöpfen (siehe unten).
Das Programm erstellt nur eine statische
java.util.List
Aufladung und stopft immer wieder neue Zeichenfolgen hinein, bis OOM ausgelöst wird. Es fängt es dann auf und stopft weiter in eine Endlosschleife (schlechte JVM ...).Testergebnis
Wie man an der Ausgabe sehen kann, wird das erste Mal, wenn OOME ausgelöst wird, eine Stapelverfolgung mitgeliefert. Danach werden nachfolgende OOMEs nur gedruckt,
java.lang.OutOfMemoryError: Java heap space
wenn sieprintStackTrace()
aufgerufen werden.Anscheinend bemüht sich die JVM, einen Stack-Trace zu drucken, wenn dies möglich ist. Wenn der Speicher jedoch sehr knapp ist, wird der Trace einfach weggelassen, wie in den anderen Antworten angegeben.
Interessant ist auch der Hash-Code des OOME. Beachten Sie, dass die ersten OOME alle unterschiedliche Hashes haben. Sobald die JVM beginnt, Stapelspuren wegzulassen, ist der Hash immer der gleiche. Dies deutet darauf hin, dass die JVM so lange wie möglich frische (vorab zugewiesene?) OOME-Instanzen verwendet. Wenn jedoch ein Push auftritt, wird nur dieselbe Instanz wiederverwendet, anstatt nichts zu werfen.
Ausgabe
Hinweis: Ich habe einige Stapelspuren abgeschnitten, um die Ausgabe besser lesbar zu machen ("[...]").
Das Programm
quelle
System.out
aberprintStackTrace()
AnwendungenSystem.err
standardmäßig. Sie würden wahrscheinlich bessere Ergebnisse erzielen, wenn Sie einen der beiden Streams konsistent verwenden.Ich bin mir ziemlich sicher, dass die JVM absolut sicherstellen wird, dass sie mindestens genug Speicher hat, um eine Ausnahme auszulösen, bevor der Speicher knapp wird.
quelle
Ausnahmen, die auf einen Versuch hinweisen, die Grenzen einer Umgebung mit verwaltetem Speicher zu verletzen, werden von der Laufzeit dieser Umgebung, in diesem Fall der JVM, behandelt. Die JVM ist ein eigener Prozess, auf dem die IL Ihrer Anwendung ausgeführt wird. Sollte ein Programm versuchen, einen Aufruf zu tätigen, der den Aufrufstapel über die Grenzen hinaus erweitert, oder mehr Speicher zuweisen, als die JVM reservieren kann, wird durch die Laufzeit selbst eine Ausnahme ausgelöst, die dazu führt, dass der Aufrufstapel abgewickelt wird. Unabhängig davon, wie viel Speicher Ihr Programm derzeit benötigt oder wie tief sein Aufrufstapel ist, hat die JVM innerhalb ihrer eigenen Prozessgrenzen genügend Speicher zugewiesen, um diese Ausnahme zu erstellen und in Ihren Code einzufügen.
quelle
Sie scheinen den von der JVM reservierten virtuellen Speicher, in dem die JVM Java-Programme ausführt, mit dem nativen Speicher des Host-Betriebssystems zu verwechseln, in dem die JVM als nativer Prozess ausgeführt wird. Die JVM auf Ihrem Computer wird in dem vom Betriebssystem verwalteten Speicher ausgeführt, nicht in dem Speicher, den die JVM für die Ausführung von Java-Programmen reserviert hat.
Weiterführende Literatur:
Und als abschließende Bemerkung, versuchen zu fangen einen java.lang.Error (und seine abgeleiteten Klassen), um einen Stacktrace drucken können Sie alle nützlichen Informationen nicht geben. Sie möchten stattdessen einen Heap-Dump.
quelle
Um die Antwort von @Graham Borland funktional weiter zu klären, führt die JVM dies beim Start folgendermaßen aus:
Später führt die JVM einen der folgenden Java-Bytecodes aus: 'new', 'anewarray' oder 'multianewarray'. Diese Anweisung veranlasst die JVM, eine Reihe von Schritten in einem Zustand ohne Speicher auszuführen:
allocate()
.allocate()
versucht, Speicher für eine neue Instanz einer bestimmten Klasse oder eines bestimmten Arrays zuzuweisen.doGC()
, die versucht, die Speicherbereinigung durchzuführen.allocate()
versucht, Speicher für die Instanz zuzuweisen.throw OOME;
und verweist auf das OOME, das sie beim Start instanziiert hat. Beachten Sie, dass es dieses OOME nicht zuweisen musste, sondern nur darauf verweist.Offensichtlich sind dies keine wörtlichen Schritte; Sie variieren in der Implementierung von JVM zu JVM, aber dies ist die Idee auf hoher Ebene.
(*) Hier geschieht ein erheblicher Arbeitsaufwand, bevor er fehlschlägt. Die JVM versucht, SoftReference-Objekte zu löschen, bei Verwendung eines Generationskollektors eine direkte Zuordnung zur dauerhaften Generation zu versuchen und möglicherweise andere Dinge wie die Finalisierung.
quelle
Die Antworten, die besagen, dass die JVM vorab zuweisen wird,
OutOfMemoryErrors
sind in der Tat richtig.Zusätzlich zum Testen, indem wir eine Situation mit zu wenig Arbeitsspeicher provozieren, können wir einfach den Heap einer JVM überprüfen (ich habe ein kleines Programm verwendet, das nur einen Ruhezustand ausführt und es mit der Hotspot-JVM von Oracle aus Java 8 Update 31 ausführt).
Mit sehen
jmap
wir, dass es 9 Instanzen von OutOfMemoryError zu geben scheint (obwohl wir viel Speicher haben):Wir können dann einen Heap-Dump generieren:
und öffnen Sie es mit Eclipse Memory Analyzer , wo eine OQL-Abfrage zeigt, dass die JVM tatsächlich
OutOfMemoryErrors
alle möglichen Nachrichten vorab zuzuweisen scheint :Der Code für die Java 8 Hotspot-JVM, der diese tatsächlich vorab zuweist, befindet sich hier und sieht folgendermaßen aus (wobei einige Teile weggelassen wurden):
und dieser Code zeigt, dass die JVM zuerst versucht, einen der vorab zugewiesenen Fehler mit Platz für eine Stapelverfolgung zu verwenden, und dann auf einen Fehler ohne Stapelverfolgung zurückgreift:
quelle
Metaspace
. Es wäre schön, wenn Sie einen Code anzeigen könnten, der auch für PermGen geeignet ist, und ihn auch mit Metaspace vergleichen könnten.