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

Lesezeit: 13 Minuten

Benutzer-Avatar
Schrittmacher

Ich bin mir bewusst, dass jedes Objekt Heap-Speicher benötigt und jede Primitive/Referenz auf dem Stack Stack-Speicher benötigt.

Wenn ich versuche, ein Objekt auf dem Heap zu erstellen und dafür nicht genügend Arbeitsspeicher vorhanden ist, erstellt die JVM eine java.lang.OutOfMemoryError auf den Haufen und wirft es mir zu.

Implizit bedeutet dies, dass die JVM beim Start etwas Speicher reserviert.

Was passiert, wenn dieser reservierte Speicher aufgebraucht ist (er würde definitiv aufgebraucht sein, lesen Sie die Diskussion unten) und die JVM nicht genügend Speicher auf dem Heap hat, um eine Instanz davon zu erstellen java.lang.OutOfMemoryError?

Hängt es einfach? Oder würde er mir einen zuwerfen null da gibt es keine Erinnerung an new eine Instanz von OOM?

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 Arbeitsspeicher reservieren?

Unabhängig davon, wie viel Speicher reserviert ist, ist es immer noch möglich, dass dieser Speicher aufgebraucht wird, wenn die JVM keine Möglichkeit hat, diesen Speicher “zurückzufordern”:

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 kürzer:

private void OnOOM(java.lang.OutOfMemoryError e) {
    try {
        e.printStackTrace();
    } catch (java.lang.OutOfMemoryError e2) {
        OnOOM(e2);
    }
}

  • Ihre Antwort wäre in hohem Maße JVM-abhängig

    – MozenRath

    13. Februar 2012 um 13:57 Uhr

  • Eine Telefonie-Bibliothek, die ich einmal (in den 90er Jahren) verwendet habe, um das abzufangen OutOfMemoryException und dann etwas tun, was das Erstellen eines großen Puffers beinhaltete …

    – Tom Hawtin – Angelleine

    13. Februar 2012 um 14:23 Uhr

  • @TomHawtin-tackline Was ist, wenn die daran beteiligten Operationen ein weiteres OOM auslösen?

    – Schrittmacher

    13. Februar 2012 um 14:26 Uhr

  • Wie bei einem Mobiltelefon ist der Akku leer, aber es hat genug Akku, um weiterhin zu spammen: “Der Akku ist leer”.

    – Kazuma

    13. Februar 2012 um 22:13 Uhr

  • “Was passiert, wenn dieser reservierte Speicher aufgebraucht ist”: Das könnte nur passieren, wenn das Programm den ersten abfängt OutOfMemoryError und einen Verweis darauf beibehalten. Es stellt sich heraus, dass das Fangen von a OutOfMemoryError ist nicht so sinnvoll, wie man meinen könnte, denn das kann man garantieren fast nichts über den Zustand Ihres Programms beim Fangen. Siehe stackoverflow.com/questions/8728866/…

    – Raedwald

    14. Februar 2012 um 13:05 Uhr

Benutzer-Avatar
Buhake Sindi

Der JVM geht nie wirklich der Arbeitsspeicher aus. Es führt eine Speicherberechnung des Heap-Stacks im Voraus durch.

Das Aufbau der JVM, Kapitel 3Abschnitt 3.5.2 heißt es:

  • Wenn Java-Virtual-Machine-Stacks dynamisch erweitert werden können und eine Erweiterung versucht wird, aber nicht genügend Speicher verfügbar gemacht werden kann, um die Erweiterung zu bewirken, oder wenn nicht genügend Speicher verfügbar gemacht werden kann, um den anfänglichen Java-Virtual-Machine-Stack für einen neuen Thread zu erstellen, wird Java virtual Maschine wirft ein OutOfMemoryError.

Zum HaufenAbschnitt 3.5.3.

  • Wenn eine Berechnung mehr Heap erfordert, als vom automatischen Speicherverwaltungssystem verfügbar gemacht werden kann, wirft die Java Virtual Machine eine OutOfMemoryError.

Es wird also 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 namens Permanent Generation Region (oder PermSpace) zuzuweisen. Wenn die Zuweisung fehlschlägt (selbst nachdem die JVM den Garbage Collector aufgerufen hat, um zu versuchen, freien Speicherplatz zuzuweisen), wird eine ausgegeben OutOfMemoryError. Auch Ausnahmen erfordern Speicherplatz, sodass der Fehler auf unbestimmte Zeit ausgegeben wird.

Weiterlesen.? Außerdem, OutOfMemoryError kann in unterschiedlichen auftreten JVM-Struktur.

  • Ich meine ja, aber würde die Java Virtual Machine nicht auch Speicher benötigen, um einen OutOfMemoryError auszulösen? Was passiert, wenn es keinen Speicher gibt, um einen OOM zu werfen?

    – Schrittmacher

    13. Februar 2012 um 15:19 Uhr

  • Aber wenn die JVM den Verweis auf dieselbe Instanz eines OOM nicht zurückgibt, stimmen Sie nicht zu, dass ihr der reservierte Speicher irgendwann ausgehen würde? (wie im Code in der Frage gezeigt)

    – Schrittmacher

    13. Februar 2012 um 15:40 Uhr


  • Erlauben Sie mir, hier auf Grahams Kommentar zu verweisen: stackoverflow.com/questions/9261705/…

    – Schrittmacher

    13. Februar 2012 um 15:42 Uhr

  • Könnte nett sein, wenn die VM ein Singleton von OOM Exception für den angegebenen Extremfall und in einem Kernkraftwerk behalten würde.

    – Johannes K

    14. Februar 2012 um 4:57 Uhr


  • @JohnK: Ich würde hoffen, dass Kernkraftwerke nicht in Java programmiert werden, genau wie Space Shuttles und Boeing 757 nicht in Java programmiert werden.

    – Dietrich Ep

    14. Februar 2012 um 7:11 Uhr

Benutzer-Avatar
Ilmari Karonen

Graham Borland scheint recht zu haben: zumindest mein JVM verwendet offenbar 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 diese Ausgabe erzeugt:

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

Übrigens, die JVM, die ich ausführe (unter Ubuntu 10.04), ist diese:

$ 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 versuchte zu sehen, was passieren würde, wenn ich gezwungen die JVM mit dem folgenden Programm vollständig aus dem Arbeitsspeicher laufen:

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 sich für immer zu wiederholen. Seltsamerweise versucht man jedoch, das Programm mit zu beenden Strg+C funktioniert nicht, sondern gibt nur folgende Meldung:

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

  • Schöner Test, derselbe für mich mit Version “1.7.0_01” Java HotSpot(TM) 64-Bit Server VM

    – Schrittmacher

    13. Februar 2012 um 17:40 Uhr

  • Interessant. Das lässt es so aussehen, als würde die JVM die Tail-Rekursion nicht vollständig verstehen … (Als ob sie eine tail-rekursive Stack-Wiederverwendung durchführt, aber nicht genug, um den gesamten Speicher zu bereinigen …)

    – Iskata

    13. Februar 2012 um 19:02 Uhr

  • Ich habe Ihren Code geändert, um zu sehen, wie viele verschiedene ich erhalte – ich bekomme den Else-Zweig immer genau 5 Mal ausgeführt.

    – Irfi

    13. Februar 2012 um 19:13 Uhr

  • @Izkata: Ich würde eher sagen, es ist eine bewusste Entscheidung, im Voraus zuzuweisen n OOMs und verwenden Sie einen davon später wieder, damit ein OOM dies kann stets geworfen werden. Die JVM von Sun/Oracle unterstützt überhaupt keine Tail-Rekursion IIRC?

    – Irfi

    13. Februar 2012 um 19:19 Uhr

  • @Izkata Die Schleife läuft anscheinend endlos, weil die JVM ständig ein und dasselbe (5. oder so) OOM wirft, nachdem ihr der Speicher ausgeht. Also hat es n Frames auf dem Stapel und es endet damit, Frames zu erstellen und zu zerstören n+1 für die Ewigkeit, was den Anschein erweckt, endlos zu laufen.

    – Irfi

    13. Februar 2012 um 19:40 Uhr

Die meisten Laufzeitumgebungen weisen beim Start vorab genügend Speicher zu oder reservieren auf andere Weise ausreichend Speicher, um mit Speicherknappheitssituationen fertig zu werden. Ich kann mir vorstellen, dass die meisten vernünftigen JVM-Implementierungen dies tun würden.

  • stackoverflow.com/questions/9261215/… : Stimmt, 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 ein weiteres OOM (98 Einheiten übrig) zuzuwerfen, und ich fange das mit einem e.printStackTrace() ab, sodass die JVM mir ein weiteres OOM (97 Einheiten übrig) zuwirft, und nun, das ist gefangen und ich wollte ..

    – Schrittmacher

    13. Februar 2012 um 14:04 Uhr


  • Genau aus diesem Grund hat OOME nie einen Stack-Trace integriert – Stack-Traces nehmen Speicherplatz in Anspruch! OOME hat erst mit dem Versuch begonnen, einen Stack-Trace in Java 6 einzufügen (blogs.oracle.com/alanb/entry/…). Ich gehe davon aus, dass, wenn der Stack-Trace nicht möglich ist, die Ausnahme ohne Stack-Trace ausgelöst wird.

    – Sean Reilly

    13. Februar 2012 um 14:07 Uhr


  • @SeanReilly Ich meine, eine Ausnahme, die keinen Stack-Trace hat, ist immer noch ein Objekt, das immer noch Speicher benötigt. Speicher wird unabhängig davon benötigt, ob ein Stack-Trace bereitgestellt wird. Stimmt es, dass ich im catch-Block eine Null abfangen würde, wenn kein Speicher mehr vorhanden ist, um ein OOM zu erstellen (kein Speicher, um auch nur eines ohne Stack-Trace zu erstellen)?

    – Schrittmacher

    13. Februar 2012 um 14:09 Uhr


  • Die JVM könnte mehrere Verweise auf eine einzelne statische Instanz der OOM-Ausnahme zurückgeben. Also auch wenn Ihr catch -Klausel versucht, mehr Speicher zu verwenden, kann die JVM einfach immer wieder dieselbe OOM-Instanz auslösen.

    – Graham Borland

    13. Februar 2012 um 14:13 Uhr

  • @TheEliteGentleman Ich stimme zu, dass dies auch sehr gute Antworten sind, aber die JVM lebt auf einer physischen Maschine. Diese Antworten erklärten nicht, wie die JVM auf magische Weise über ausreichend Speicher verfügen könnte, um immer eine Instanz von OOM bereitzustellen. “Es ist immer die gleiche Instanz” scheint das Rätsel zu lösen.

    – Schrittmacher

    13. Februar 2012 um 15:06 Uhr


Als ich das letzte Mal in Java gearbeitet und einen Debugger verwendet habe, hat der Heap-Inspektor gezeigt, 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, dass ihm der Speicher ausgeht.

Benutzer-Avatar
Andreas Dölk

Aus der JVM-Spezifikation, Kapitel 3.5.2:

Wenn Java-Virtual-Machine-Stacks dynamisch erweitert werden können und eine Erweiterung versucht wird, aber nicht genügend Speicher verfügbar gemacht werden kann, um die Erweiterung zu bewirken, oder wenn nicht genügend Speicher verfügbar gemacht werden kann, um den anfänglichen Java-Virtual-Machine-Stack für einen neuen Thread zu erstellen, Die Java Virtual Machine wirft eine OutOfMemoryError.

Jede Java Virtual Machine muss garantieren, dass sie eine wirft OutOfMemoryError. Das bedeutet, dass es in der Lage sein muss, eine Instanz von zu erstellen OutOfMemoryError (oder im Voraus erstellt haben), auch wenn kein Heap-Platz mehr vorhanden ist.

Obwohl es nicht garantieren muss, dass genug Speicher übrig ist, um es abzufangen und einen schönen Stacktrace 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. Aber eine solche Implementierung würde gegen die Forderung von oben verstoßen.

Es ist nicht erforderlich, dass die ausgelösten Instanzen von OutOfMemoryError sind einzigartig oder werden auf Anfrage erstellt. Eine JVM könnte genau eine Instanz von vorbereiten OutOfMemoryError während des Starts und werfen Sie dies, wenn der Heap-Speicherplatz knapp wird – was in einer normalen Umgebung einmal der Fall ist. Mit anderen Worten: die Instanz von OutOfMemoryError dass wir sehen, könnte ein Singleton sein.

  • Ich denke, um dies zu implementieren, müsste es auf die Aufzeichnung des Stack-Trace verzichten, wenn der Platz knapp wäre.

    – Raedwald

    15. Februar 2012 um 13:10 Uhr

  • @Raedwald: Genau das tut die Oracle-VM, siehe meine Antwort.

    – schleske

    9. März 2012 um 11:39 Uhr

Interessante Frage :-). Während die anderen die theoretischen Aspekte gut erklärt haben, habe ich mich entschieden, es auszuprobieren. Dies ist auf 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 Statik java.util.List, und stopft immer wieder neue Saiten hinein, bis OOM geworfen wird. Es fängt es dann ab und stopft es in einer Endlosschleife weiter (arme JVM …).

Testergebnis

Wie man an der Ausgabe sehen kann, wird OOME die ersten vier Male geworfen, es kommt mit einem Stack-Trace. Danach drucken nachfolgende OOMEs nur noch java.lang.OutOfMemoryError: Java heap space wenn printStackTrace() wird aufgerufen.

Anscheinend bemüht sich die JVM, einen Stack-Trace zu drucken, wenn dies möglich ist, aber wenn der Speicher sehr knapp ist, wird der Trace einfach weggelassen, genau wie die anderen Antworten vermuten lassen.

Interessant ist auch der Hashcode des OOME. Beachten Sie, dass die ersten paar OOME alle unterschiedliche Hashes haben. Sobald die JVM damit beginnt, Stack-Traces wegzulassen, ist der Hash immer derselbe. Dies deutet darauf hin, dass die JVM so lange wie möglich frische (vorab zugewiesene?) OOME-Instanzen verwendet, aber wenn es hart auf hart kommt, wird sie einfach dieselbe Instanz wiederverwenden, anstatt nichts zu werfen.

Ausgabe

Hinweis: Ich habe einige Stack-Traces abgeschnitten, um die Ausgabe leichter 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);
            }
        }

    }
}

  • Ich denke, um dies zu implementieren, müsste es auf die Aufzeichnung des Stack-Trace verzichten, wenn der Platz knapp wäre.

    – Raedwald

    15. Februar 2012 um 13:10 Uhr

  • @Raedwald: Genau das tut die Oracle-VM, siehe meine Antwort.

    – schleske

    9. März 2012 um 11:39 Uhr

Benutzer-Avatar
Oskar Gomez

Ich bin mir ziemlich sicher, dass die JVM absolut sicherstellen wird, dass sie mindestens über genügend Speicher verfügt, um eine Ausnahme auszulösen, bevor ihr der Speicher ausgeht.

  • Aber sie würden diese Situation treffen, egal wie viel Speicher reserviert ist: stackoverflow.com/questions/9261705/…

    – Schrittmacher

    13. Februar 2012 um 14:06 Uhr

1354520cookie-checkWas passiert, wenn nicht genügend Arbeitsspeicher vorhanden ist, um einen OutOfMemoryError auszulösen?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy