Was passiert, wenn nicht genügend Speicher vorhanden ist, um einen OutOfMemoryError auszulösen?

207

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, nullda es keine Erinnerung an neweine 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);
    }
}
Pacerier
quelle
2
Ihre Antwort wäre in hohem Maße JVM-abhängig
MozenRath
23
Eine Telefoniebibliothek, die ich einmal benutzt habe (in den 90ern), hat das OutOfMemoryException
abgefangen
@ TomHawtin-Tackline Was ist, wenn die damit verbundenen Operationen einen weiteren OOM auslösen?
Pacerier
38
Wie bei einem Mobiltelefon geht der Akku aus, aber der Akku reicht aus, um weiterhin zu spammen. "Ihnen geht der Akku aus".
Kazuma
1
"Was passiert, wenn dieser reservierte Speicher aufgebraucht ist?": Dies kann nur passieren, wenn das Programm den ersten OutOfMemoryErrorabfängt und einen Verweis darauf behält. Es stellt sich heraus, dass das Abfangen von a OutOfMemoryErrornicht 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/…
Raedwald

Antworten:

145

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:

  • Wenn Java Virtual Machine-Stapel dynamisch erweitert werden können und eine Erweiterung versucht wird, jedoch nicht genügend Speicher zur Verfügung gestellt werden kann, um die Erweiterung durchzuführen, oder wenn nicht genügend Speicher verfügbar ist, um den ersten Java Virtual Machine-Stapel für einen neuen Thread, den Java Virtual, zu erstellen Maschine wirft eine OutOfMemoryError.

Für Heap Abschnitt 3.5.3.

  • Wenn für eine Berechnung mehr Heap erforderlich ist, als vom automatischen Speicherverwaltungssystem bereitgestellt werden kann, gibt die virtuelle Java-Maschine eine aus OutOfMemoryError.

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 OutOfMemoryErrorkann in unterschiedlicher JVM-Struktur auftreten.

Buhake Sindi
quelle
10
Ich meine ja, aber würde die Java Virtual Machine nicht auch Speicher benötigen, um einen OutOfMemoryError auszulösen? Was passiert, wenn kein Speicher zum Auslösen eines OOM vorhanden ist?
Pacerier
5
Aber wenn die JVM den Verweis nicht auf dieselbe Instanz eines OOM zurückgibt, stimmen Sie nicht zu, dass der reservierte Speicher irgendwann ausgehen würde? (wie im Code in der Frage gezeigt)
Pacerier
1
Gestatten Sie mir, hier auf Grahams Kommentar zu verweisen
Pacerier
2
Könnte schön sein, wenn die VM einen Singleton der OOM-Ausnahme für den angegebenen Extremfall und in einem Kernkraftwerk beibehalten würde.
John K
8
@JohnK: Ich würde hoffen, dass Kernkraftwerke nicht in Java programmiert sind, genau wie Space Shuttles und Boeing 757 nicht in Java programmiert sind.
Dietrich Epp
64

Graham Borland scheint recht zu haben : Zumindest verwendet meine JVM anscheinend OutOfMemoryErrors wieder. Um dies zu testen, habe ich ein einfaches Testprogramm geschrieben:

class OOMTest {
    private static void test (OutOfMemoryError o) {
        try {
            for (int n = 1; true; n += n) {
                int[] foo = new int[n];
            }
        } catch (OutOfMemoryError e) {
            if (e == o)
                System.out.println("Got the same OutOfMemoryError twice: " + e);
            else test(e);
        }
    }
    public static void main (String[] args) {
        test(null);
    }
}

Wenn Sie es ausführen, wird folgende Ausgabe erstellt:

$ javac OOMTest.java && java -Xmx10m OOMTest 
Got the same OutOfMemoryError twice: java.lang.OutOfMemoryError: Java heap space

Übrigens ist die JVM, die ich verwende (unter Ubuntu 10.04), folgende:

$ java -version
java version "1.6.0_26"
Java(TM) SE Runtime Environment (build 1.6.0_26-b03)
Java HotSpot(TM) 64-Bit Server VM (build 20.1-b02, mixed mode)

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:

class OOMTest2 {
    private static void test (int n) {
        int[] foo;
        try {
            foo = new int[n];
            test(n * 2);
        }
        catch (OutOfMemoryError e) {
            test((n+1) / 2);
        }
    }
    public static void main (String[] args) {
        test(1);
    }
}

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

Ilmari Karonen
quelle
Schöner Test, das gleiche für mich mit Version "1.7.0_01" Java HotSpot (TM) 64-Bit-Server-VM
Pacerier
Interessant. Das sieht so aus, als ob die JVM die Schwanzrekursion nicht vollständig versteht ... (Als ob sie eine rekursive Stapelwiederverwendung durchführt, aber nicht genug, um den gesamten Speicher zu bereinigen ...)
Izkata
Sie haben Ihren Code geändert, um zu sehen, wie viele verschiedene ich erhalte. Der else-Zweig wird immer genau fünfmal ausgeführt.
Irfy
@Izkata: Ich würde eher sagen, dass es eine bewusste Entscheidung ist, nOOMs 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?
Irfy
10
@Izkata Die Schleife läuft anscheinend endlos, weil die JVM ständig ein und dasselbe (5. oder so) OOM auslöst, nachdem ihr Speicherplatz ausgeht. Es hat also nFrames auf dem Stapel und es schafft und zerstört Frames n+1für die Ewigkeit, was den Anschein erweckt, endlos zu laufen.
Irfy
41

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.

Graham Borland
quelle
1
stackoverflow.com/questions/9261215/… : Richtig, aber wenn die JVM dafür 100 Speichereinheiten reserviert und 1 Einheit für das erste OOM ausgegeben hat, was passiert, wenn ich in meinem OOM-Catch-Block e.printStackTrace () mache? e.printStackTrace () benötigt ebenfalls Speicher. Dann würde die JVM eine weitere Speichereinheit ausgeben, um mir eine weitere OOM zu werfen (noch 98 Einheiten), und das fange ich mit einem e.printStackTrace () ab, sodass die JVM mir eine weitere OOM wirft (noch 97 Einheiten übrig), und nun, das wird abgefangen und ich wollte ..
Pacerier
3
Genau aus diesem Grund hat OOME nie einen Stack-Trace eingefügt - Stack-Traces beanspruchen Speicher! OOME hat erst versucht, einen Stack-Trace in Java 6 ( blogs.oracle.com/alanb/entry/… ) aufzunehmen. Ich gehe davon aus, dass die Ausnahme ohne Stack-Trace ausgelöst wird, wenn der Stack-Trace nicht möglich ist.
Sean Reilly
1
@ SeanReilly Ich meine, eine Ausnahme ohne Stack-Trace ist immer noch ein Objekt, das immer noch Speicher benötigt. Speicher wird benötigt, unabhängig davon, ob eine Stapelverfolgung bereitgestellt wird. Stimmt es, dass ich im catch-Block eine Null abfangen würde, wenn kein Speicher mehr zum Erstellen eines OOM vorhanden ist (kein Speicher zum Erstellen eines OOMs ohne Stapelverfolgung)?
Pacerier
17
Die JVM kann mehrere Verweise auf eine einzelne statische Instanz der OOM-Ausnahme zurückgeben. Selbst wenn Ihre catchKlausel versucht, mehr Speicher zu verwenden, kann die JVM immer wieder dieselbe OOM-Instanz auslösen.
Graham Borland
1
@TheEliteGentleman Ich stimme zu, dass dies auch sehr gute Antworten sind, aber die JVM lebt auf einer physischen Maschine. Diese Antworten haben nicht erklärt, wie die JVM auf magische Weise über genügend Speicher verfügen kann, um immer eine Instanz von OOM bereitzustellen. "Es ist immer die gleiche Instanz" scheint das Rätsel zu lösen.
Pacerier
23

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.

Benzado
quelle
12

Aus der JVM-Spezifikation, Kapitel 3.5.2:

Wenn Java Virtual Machine-Stapel dynamisch erweitert werden können und eine Erweiterung versucht wird, jedoch nicht genügend Speicher zur Verfügung gestellt werden kann, um die Erweiterung durchzuführen, oder wenn nicht genügend Speicher verfügbar ist, um den ersten Java Virtual Machine-Stapel für einen neuen Thread, den Java Virtual,OutOfMemoryError zu erstellen Maschine wirft eine .

Jede Java Virtual Machine muss garantieren, dass sie eine wirft OutOfMemoryError. OutOfMemoryErrorDies 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 OutOfMemoryErroreindeutig sind oder bei Bedarf erstellt werden. Eine JVM kann OutOfMemoryErrorwä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 Instanz OutOfMemoryError, die wir sehen, könnte ein Singleton sein.

Andreas Dolk
quelle
Ich denke, um dies zu implementieren, müsste es auf die Aufzeichnung des Stack-Trace verzichten, wenn der Platz knapp wäre.
Raedwald
@ Raedwald: Tatsächlich ist dies genau das, was die Oracle VM tut, siehe meine Antwort.
Sleske
11

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.ListAufladung 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 spacewenn sie printStackTrace()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 ("[...]").

iteration 0
iteration 100000
iteration 200000
iteration 300000
iteration 400000
iteration 500000
iteration 600000
iteration 700000
iteration 800000
iteration 900000
iteration 1000000
iteration 1100000
iteration 1200000
iteration 1300000
iteration 1400000
iteration 1500000
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1069480624
Keep on trying...
java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Unknown Source)
    at java.util.Arrays.copyOf(Unknown Source)
    at java.util.ArrayList.ensureCapacity(Unknown Source)
    at java.util.ArrayList.add(Unknown Source)
    at testsl.Div.gobbleUpMemory(Div.java:23)
    at testsl.Div.exhaustMemory(Div.java:12)
    at testsl.Div.main(Div.java:7)
java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Unknown Source)
[...]
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 616699029
Keep on trying...
java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Unknown Source)
[...]
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 2136955031
Keep on trying...
java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Unknown Source)
[...]
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1535562945
Keep on trying...
java.lang.OutOfMemoryError: Java heap space
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1734048134
Keep on trying...
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1734048134
Keep on trying...
java.lang.OutOfMemoryError: Java heap space
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1734048134
Keep on trying...
[...]

Das Programm

public class Div{
    static java.util.List<String> list = new java.util.ArrayList<String>();

    public static void main(String[] args) {
        exhaustMemory();
    }

    private static void exhaustMemory() {
        try {
            gobbleUpMemory();
        } catch (OutOfMemoryError e) {
            System.out.println("Ouch: " + e+"; hash: "+e.hashCode());
            e.printStackTrace();
            System.out.println("Keep on trying...");
            exhaustMemory();
        }
    }

    private static void gobbleUpMemory() {
        for (int i = 0; i < 10000000; i++) {
            list.add(new String("some random long string; use constructor to force new instance"));
            if (i % 10000000== 0) {
                System.out.println("iteration "+i);
            }
        }

    }
}
sleske
quelle
Wenn Push ausgeführt wird, kann kein Speicher für OOME zugewiesen werden, sodass die bereits erstellten gelöscht werden.
Buhake Sindi
1
Minor Anmerkung: einige Ihrer Ausgabe scheint die Sequenz heraus, vermutlich , weil man den Druck , System.outaber printStackTrace()Anwendungen System.errstandardmäßig. Sie würden wahrscheinlich bessere Ergebnisse erzielen, wenn Sie einen der beiden Streams konsistent verwenden.
Ilmari Karonen
@IlmariKaronen: Ja, das habe ich bemerkt. Es ist nur ein Beispiel, also war es egal. Offensichtlich würden Sie es nicht im Produktionscode verwenden.
Sleske
Es stimmt, ich habe nur eine Weile gebraucht, um herauszufinden, was los war, als ich mir die Ausgabe zum ersten Mal ansah.
Ilmari Karonen
6

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.

Oscar Gomez
quelle
1
Aber sie würden diese Situation treffen, egal wie viel Speicher reserviert ist: stackoverflow.com/questions/9261705/…
Pacerier
4

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.

KeithS
quelle
"Die JVM hat innerhalb ihrer eigenen Prozessgrenzen genügend Speicher zugewiesen, um diese Ausnahme zu erstellen." Wenn Ihr Code jedoch einen Verweis auf diese Ausnahme enthält und nicht wiederverwendet werden kann, wie kann er dann eine weitere erstellen? Oder schlagen Sie vor, dass es ein spezielles Singleton OOME-Objekt hat?
Raedwald
Das schlage ich nicht vor. Wenn Ihr Programm jede Ausnahme abschließt und festhält, die jemals erstellt wurde, einschließlich derjenigen, die von der JVM oder dem Betriebssystem erstellt und eingefügt wurden, überschreitet die JVM selbst möglicherweise eine vom Betriebssystem festgelegte Grenze, und das Betriebssystem fährt sie für eine GPF oder herunter ähnlicher Fehler. Das ist jedoch in erster Linie schlechtes Design; Ausnahmen sollten entweder behandelt und dann außerhalb des Geltungsbereichs liegen oder verworfen werden. Und Sie sollten NIEMALS versuchen, ein SOE oder OOME zu fangen und fortzusetzen. Abgesehen von "Aufräumen", damit Sie ordnungsgemäß beenden können, können Sie in diesen Situationen nichts tun, um die Ausführung fortzusetzen.
KeithS
"Das ist in erster Linie schlechtes Design": schreckliches Design. Aber würde die JVM pedantisch der Spezifikation entsprechen, wenn sie auf diese Weise versagen würde ?
Raedwald
4

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.

Michael Tiffany
quelle
4

Um die Antwort von @Graham Borland funktional weiter zu klären, führt die JVM dies beim Start folgendermaßen aus:

private static final OutOfMemoryError OOME = new OutOfMemoryError();

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:

  1. Rufen Sie beispielsweise eine native Funktion auf allocate(). allocate()versucht, Speicher für eine neue Instanz einer bestimmten Klasse oder eines bestimmten Arrays zuzuweisen.
  2. Diese Zuordnungsanforderung schlägt fehl, sodass die JVM beispielsweise eine andere native Funktion aufruft doGC(), die versucht, die Speicherbereinigung durchzuführen.
  3. Wenn diese Funktion zurückkehrt, wird erneut allocate()versucht, Speicher für die Instanz zuzuweisen.
  4. Wenn dies fehlschlägt (*), führt die JVM in allocate () einfach a aus 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.

ahawtho
quelle
3

Die Antworten, die besagen, dass die JVM vorab zuweisen wird, OutOfMemoryErrorssind 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 jmapwir, dass es 9 Instanzen von OutOfMemoryError zu geben scheint (obwohl wir viel Speicher haben):

> jmap -histo 12103 | grep OutOfMemoryError
 71: 9 288 java.lang.OutOfMemoryError
170: 1 32 [Ljava.lang.OutOfMemoryError;

Wir können dann einen Heap-Dump generieren:

> jmap -dump: format = b, file = heap.hprof 12315

und öffnen Sie es mit Eclipse Memory Analyzer , wo eine OQL-Abfrage zeigt, dass die JVM tatsächlich OutOfMemoryErrorsalle möglichen Nachrichten vorab zuzuweisen scheint :

Geben Sie hier die Bildbeschreibung ein

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

...
// Setup preallocated OutOfMemoryError errors
k = SystemDictionary::resolve_or_fail(vmSymbols::java_lang_OutOfMemoryError(), true, CHECK_false);
k_h = instanceKlassHandle(THREAD, k);
Universe::_out_of_memory_error_java_heap = k_h->allocate_instance(CHECK_false);
Universe::_out_of_memory_error_metaspace = k_h->allocate_instance(CHECK_false);
Universe::_out_of_memory_error_class_metaspace = k_h->allocate_instance(CHECK_false);
Universe::_out_of_memory_error_array_size = k_h->allocate_instance(CHECK_false);
Universe::_out_of_memory_error_gc_overhead_limit =
  k_h->allocate_instance(CHECK_false);

...

if (!DumpSharedSpaces) {
  // These are the only Java fields that are currently set during shared space dumping.
  // We prefer to not handle this generally, so we always reinitialize these detail messages.
  Handle msg = java_lang_String::create_from_str("Java heap space", CHECK_false);
  java_lang_Throwable::set_message(Universe::_out_of_memory_error_java_heap, msg());

  msg = java_lang_String::create_from_str("Metaspace", CHECK_false);
  java_lang_Throwable::set_message(Universe::_out_of_memory_error_metaspace, msg());
  msg = java_lang_String::create_from_str("Compressed class space", CHECK_false);
  java_lang_Throwable::set_message(Universe::_out_of_memory_error_class_metaspace, msg());

  msg = java_lang_String::create_from_str("Requested array size exceeds VM limit", CHECK_false);
  java_lang_Throwable::set_message(Universe::_out_of_memory_error_array_size, msg());

  msg = java_lang_String::create_from_str("GC overhead limit exceeded", CHECK_false);
  java_lang_Throwable::set_message(Universe::_out_of_memory_error_gc_overhead_limit, msg());

  msg = java_lang_String::create_from_str("/ by zero", CHECK_false);
  java_lang_Throwable::set_message(Universe::_arithmetic_exception_instance, msg());

  // Setup the array of errors that have preallocated backtrace
  k = Universe::_out_of_memory_error_java_heap->klass();
  assert(k->name() == vmSymbols::java_lang_OutOfMemoryError(), "should be out of memory error");
  k_h = instanceKlassHandle(THREAD, k);

  int len = (StackTraceInThrowable) ? (int)PreallocatedOutOfMemoryErrorCount : 0;
  Universe::_preallocated_out_of_memory_error_array = oopFactory::new_objArray(k_h(), len, CHECK_false);
  for (int i=0; i<len; i++) {
    oop err = k_h->allocate_instance(CHECK_false);
    Handle err_h = Handle(THREAD, err);
    java_lang_Throwable::allocate_backtrace(err_h, CHECK_false);
    Universe::preallocated_out_of_memory_errors()->obj_at_put(i, err_h());
  }
  Universe::_preallocated_out_of_memory_error_avail_count = (jint)len;
}
...

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:

oop Universe::gen_out_of_memory_error(oop default_err) {
  // generate an out of memory error:
  // - if there is a preallocated error with backtrace available then return it wth
  //   a filled in stack trace.
  // - if there are no preallocated errors with backtrace available then return
  //   an error without backtrace.
  int next;
  if (_preallocated_out_of_memory_error_avail_count > 0) {
    next = (int)Atomic::add(-1, &_preallocated_out_of_memory_error_avail_count);
    assert(next < (int)PreallocatedOutOfMemoryErrorCount, "avail count is corrupt");
  } else {
    next = -1;
  }
  if (next < 0) {
    // all preallocated errors have been used.
    // return default
    return default_err;
  } else {
    // get the error object at the slot and set set it to NULL so that the
    // array isn't keeping it alive anymore.
    oop exc = preallocated_out_of_memory_errors()->obj_at(next);
    assert(exc != NULL, "slot has been used already");
    preallocated_out_of_memory_errors()->obj_at_put(next, NULL);

    // use the message from the default error
    oop msg = java_lang_Throwable::message(default_err);
    assert(msg != NULL, "no message");
    java_lang_Throwable::set_message(exc, msg);

    // populate the stack trace and return it.
    java_lang_Throwable::fill_in_stack_trace_of_preallocated_backtrace(exc);
    return exc;
  }
}
Johan Kaving
quelle
Guter Beitrag, ich akzeptiere dies eine Woche lang als Antwort, um es besser sichtbar zu machen, bevor ich zur vorherigen Antwort zurückkehre.
Pacerier
In Java 8 und höher wurde der Speicherplatz für die permanente Generierung vollständig entfernt, und Sie können die Größe des Heapspeicherzuordnungsspeichers in Java 8 und höher nicht mehr angeben, da die Speicherverwaltung für dynamische Klassenmetadaten aufgerufen wurde 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.
Buhake Sindi
@BuhakeSindi - Ich verstehe nicht, was die ständige Generation damit zu tun hat. Neue Objekte werden in der permanenten Generation nicht zugewiesen, wie Sie in Ihrer Antwort angeben. Sie erwähnen auch nie die Tatsache, dass OutOfMemoryErrors vorab zugewiesen sind (dies ist die eigentliche Antwort auf die Frage).
Johan Kaving
1
Ok, ich sage, dass aus Java 8 die Objektzuweisung dynamisch berechnet wird, während sie vor der Zuweisung auf einem vordefinierten Heap-Speicherplatz möglicherweise vorher zugewiesen wurde. Obwohl OOME vorab zugewiesen wurden, wird eine "Berechnung" durchgeführt, um zu bestimmen, ob das OOME ausgelöst werden muss (daher verweise ich auf die JLS-Spezifikation).
Buhake Sindi
1
Die Größe des Java-Heapspeichers ist in Java 8 genauso vordefiniert wie zuvor. Die permanente Generation war Teil des Heaps, der Klassenmetadaten, internierte Zeichenfolgen und Klassenstatik enthielt. Es hatte eine begrenzte Größe, die getrennt von der Gesamtgröße des Heapspeichers angepasst werden musste. In Java 8 wurden die Klassen-Metadaten in den nativen Speicher verschoben und interne Zeichenfolgen und Klassenstatiken in den regulären Java-Heap verschoben (siehe z. B. hier: infoq.com/articles/Java-PERMGEN-Removed ).
Johan Kaving