Der Heap ist halb leer, die JVM stirbt trotzdem. Metaspace liegt im nativen Speicher und hat eine eigene Grenze – ein größerer Heap hilft nicht.
Aufwand:
ca. 25 Min.
Gilt für:
JDK 21, JDK 25
Das Symptom
Die Anwendung läuft, oft tagelang, und bricht dann mit java.lang.OutOfMemoryError: Metaspace ab. Die Überwachung zeigt gleichzeitig einen Heap, der bequem im grünen
Bereich liegt. Besonders gern trifft es Anwendungsserver nach dem dritten oder
vierten Redeploy derselben Anwendung, ohne dass zwischendurch jemand Code geändert
hätte.
Wer daraufhin -Xmx erhöht, erlebt das Gegenteil von Besserung: Der Fehler kommt
wieder, manchmal früher.
Die „Layer 8“-Wahrheit
Der Name der Ausnahme ist identisch, der Speicherbereich ist es nicht.
OutOfMemoryError ist ein Sammelbegriff, und die Detail-Meldung dahinter ist der
eigentliche Fehlercode. Metaspace bezeichnet den Bereich, in dem die JVM ihre
interne Darstellung von Klassen ablegt – nicht Objekte, sondern die Bauanleitungen
für Objekte. Dieser Bereich liegt laut Oracle im nativen Speicher, nicht im Heap.
Am Heap-Regler zu drehen ist deshalb ungefähr so wirksam, wie den Tank zu
vergrößern, weil die Bremsflüssigkeit fehlt.
Interessanter wird es beim zweiten Ratschlag aus derselben Quelle: Den Heap
verkleinern, um Metaspace Platz zu machen. Beide werden aus demselben Adressraum
bedient. Das ist kein Trick, sondern schlichte Arithmetik – und Oracle hängt die
Einschränkung gleich an, dass sich der Handel nur bei reichlich freiem Heap lohnt.
Bleibt die unangenehme Variante. Steigt der Metaspace-Verbrauch mit jedem Redeploy
monoton an, ist keine Grenze zu klein, sondern etwas hält alte Klassenlader fest.
Solange auch nur eine Referenz auf einen ausrangierten Lader existiert, darf die JVM
dessen gesamte Klassen-Metadaten nicht freigeben – und ein Anwendungsserver erzeugt
bei jedem Deploy einen neuen. Eine höhere Grenze verlängert dann nur den Abstand
zwischen den Abstürzen und verlängert vor allem den Heap-Dump, den am Ende doch
jemand lesen muss.
Die echte, verifizierte Lösung
1000 % faktenbasiert. Jeder Befehl ist real, jeder Pfad existiert. Schritt für Schritt:
1.
Die Detail-Meldung wirklich lesen
Die JVM hängt an jeden OutOfMemoryError eine Detail-Meldung an, und die entscheidet über alles Weitere. Steht dort Java heap space, ist der Heap gemeint; steht dort Metaspace, sind es die Klassen-Metadaten im nativen Speicher. Zwei völlig verschiedene Baustellen mit demselben Ausnahmenamen – wer das verwechselt, dreht 20 Minuten lang am falschen Regler.
2.
Prüfen, ob überhaupt eine Grenze gesetzt ist
Oracles Handlungsanweisung beginnt mit der Bedingung, dass MaxMetaspaceSize auf der Kommandozeile angegeben wurde. Ist sie es nicht, steht der Wert praktisch auf unbegrenzt, und der Fehler bedeutet dann etwas anderes: Der native Speicher der Maschine oder des Containers ist zuerst ausgegangen. Erst den Ist-Zustand ansehen, dann handeln.
Beim nächsten Absturz einen Speicherabzug erzwingen
Raten hilft hier nicht weiter – man braucht die Liste der geladenen Klassenlader. Die JVM schreibt auf Wunsch beim Auftreten des Fehlers automatisch einen Heap-Dump. Ohne HeapDumpPath landet die Datei im aktuellen Arbeitsverzeichnis und heißt java_pid<pid>.hprof.
Wurde MaxMetaspaceSize explizit gesetzt und ist der Bedarf schlicht gewachsen, ist die Anhebung die von Oracle genannte Maßnahme. Das ist die richtige Antwort auf eine zu eng gezogene Grenze, aber die falsche auf ein Leck: Wenn der Verbrauch monoton steigt, kauft eine höhere Grenze nur Zeit.
java -XX:MaxMetaspaceSize=512m -jar app.jar
5.
Den Heap verkleinern, statt ihn zu vergrößern
Die zweite Maßnahme aus der Oracle-Dokumentation klingt widersinnig und ist es nicht: Metaspace wird aus demselben Adressraum wie der Java-Heap bedient, ein kleinerer Heap gibt also Platz frei. Oracle schränkt das ausdrücklich ein – der Handel lohnt sich nur, wenn im Heap tatsächlich reichlich Luft ist.
Häufige Fragen (FAQ)
?Ich habe den Heap mit -Xmx verdoppelt und der Fehler kommt trotzdem. Warum?
Weil der Heap nicht das Problem ist. Klassen-Metadaten legt die JVM im Metaspace ab, und der liegt im nativen Speicher, nicht im Heap. Oracle beschreibt sogar den umgekehrten Weg: Metaspace wird aus demselben Adressraum wie der Java-Heap bedient, ein kleinerer Heap schafft also mehr Platz für Metaspace. Ein größerer Heap verschärft die Lage.
?Bei jedem Redeploy im Anwendungsserver kommt der Fehler ein Stück früher. Ist das normal?
Normal nicht, aber typisch. Beim Redeploy lädt der Server dieselben Klassen ein weiteres Mal über einen neuen Klassenlader. Bleibt eine Referenz auf den alten Lader hängen, kann die JVM dessen Metadaten nicht freigeben, und der Metaspace wächst mit jedem Zyklus. Ein Neustart des Prozesses schiebt das Problem nur auf.
Belege
Diese Seite ist gegen die folgenden Primärquellen geprüft – Hersteller, Standard oder Gesetzestext. Wer es nicht glaubt, muss es nicht: Da steht, wo es nachzulesen ist.
jcmd -l listet nichts oder nur sich selbst, obwohl die JVM läuft. Schuld ist fast immer der falsche Benutzer – und sudo macht es schlimmer, nicht besser.