W3docs

Java Garbage Collection

Wie Java Garbage Collection funktioniert: Erreichbarkeit, GC-Roots, generationaler Heap, Mark-Sweep-Compact, Referenztypen und Collector-Auswahl.

In Java rufen Sie niemals free() auf. Die JVM verfolgt jedes Objekt, das Sie auf dem Heap allozieren, und wenn ein Objekt von Ihrem laufenden Programm nicht mehr erreicht werden kann, gibt der Garbage Collector (GC) seinen Speicher automatisch frei. Sie schreiben Code, der Objekte erzeugt; der GC räumt still hinter Ihnen auf. Zu verstehen, wie er entscheidet, was Garbage ist — und wo im Heap er sucht — ist der Unterschied zwischen Code, der skaliert, und Code, der unter Last ins Stocken gerät.

Diese Seite behandelt, wie der GC entscheidet, was er behält (Erreichbarkeit), wie der Heap für schnelle Collection aufgeteilt ist, den Mark-Sweep-Compact-Algorithmus, die vier Referenzstärken, die Wahl eines Collectors und eine ausführbare Demo, die Collection beobachtbar macht.

Erreichbarkeit und GC-Roots

Der GC sucht nicht nach Objekten, mit denen Sie „fertig" sind. Er sucht nach Objekten, die noch erreichbar sind. Ausgehend von einer Menge von GC-Roots folgt er jeder Referenz. Alles, was er erreichen kann, ist lebendig; alles andere ist Garbage, unabhängig davon, ob Sie glauben, es noch zu benötigen.

GC-RootBeispiel
Lokale VariablenEine Referenz auf dem Stack eines laufenden Threads
Statische Felderstatic final Logger LOG = ...
Aktive ThreadsEin lebendiges Thread-Objekt
JNI-ReferenzenObjekte, die von nativem Code gehalten werden

Eine Referenz auf null zu setzen (oder sie aus dem Gültigkeitsbereich fallen zu lassen) löscht nichts — es entfernt nur einen Pfad zum Objekt. Das Objekt wird nur dann sammelbar, wenn kein Pfad von einer Root mehr übrig ist.

Object a = new Object();   // reachable via local variable 'a'
Object b = a;              // now two references point to the same object
a = null;                  // still reachable through 'b' — not garbage
b = null;                  // now unreachable — eligible for collection

Der generationale Heap

Die meisten Objekte sterben jung — ein Request-Scope, eine Schleifenvariable, ein Zwischenstring. Die JVM nutzt diese schwache generationale Hypothese, indem sie den Heap in Regionen aufteilt und den jungen Bereich viel häufiger sammelt als den alten.

RegionEnthältGesammelt
Young (Eden + 2 Survivor-Spaces)Neu allozierte ObjekteHäufig, durch einen schnellen Minor GC
Old (Tenured)Objekte, die mehrere Minor GCs überlebt habenSelten, durch einen langsameren Major/Full GC
MetaspaceKlassen-Metadaten (nicht Ihre Objekte)Wenn Classloader entladen werden

Neue Objekte landen in Eden. Ein Minor GC kopiert die wenigen Überlebenden in einen Survivor-Space; Objekte, die weiter überleben, werden schließlich in die alte Generation befördert. Da Minor GCs nur die kleine junge Region scannen, sind sie günstig — weshalb kurzlebige Allokation in Java schnell ist. (Wie Stack und Heap sich unterscheiden, erfahren Sie unter Stack vs Heap; zur Rolle der JVM, die den Heap hostet, siehe JVM Architecture.)

Mark, Sweep, Compact

Eine Collection läuft in Phasen ab. Zuerst markiert sie jedes erreichbare Objekt, indem sie den Graphen von den Roots aus durchläuft. Dann sweept sie und gibt die nicht markierten Objekte frei. Viele Collectors fügen eine Compact-Phase hinzu, die überlebende Objekte zusammenschiebt, sodass freier Speicher ein zusammenhängender Block ist — was die Allokation zu einem einfachen Pointer-Bump macht und Fragmentierung verhindert.

// Pseudocode of what the collector does for you:
// 1. mark:    visit(roots); for each reachable object, set live = true
// 2. sweep:   for each object on the heap, if !live -> reclaim its memory
// 3. compact: move survivors next to each other, update references

Sie können eine Collection mit System.gc() vorschlagen, aber das ist nur ein Hinweis — die JVM kann ihn ignorieren. Verlassen Sie sich niemals auf Korrektheit; behandeln Sie ihn als Diagnosewerkzeug, nicht als Speicherverwaltungsstrategie.

Referenzstärke

Nicht jede Referenz hält ein Objekt gleich stark am Leben. Das Paket java.lang.ref erlaubt es Ihnen, dem GC mitzuteilen, wie dringend Sie ein Objekt behalten möchten, was die Grundlage speichersensitiver Caches ist.

ReferenzGC-Verhalten
Stark (gewöhnliches =)Wird niemals gesammelt, solange erreichbar
SoftReferenceWird nur gesammelt, wenn Speicher knapp ist — gut für Caches
WeakReferenceWird beim nächsten GC gesammelt, sobald keine starken Refs mehr vorhanden sind
PhantomReferenceWird verwendet, um Aufräumarbeiten nach der Collection zu planen
import java.lang.ref.WeakReference;

byte[] data = new byte[1024];
WeakReference<byte[]> ref = new WeakReference<>(data);
data = null;            // drop the only strong reference
// After the next GC, ref.get() may return null.

Speicherlecks können trotzdem auftreten

Ein Garbage Collector befreit Sie von hängenden Zeigern und doppeltem Freigeben, aber nicht von Lecks. Ein Java-Speicherleck ist ein Objekt, das Sie nicht mehr verwenden, das aber noch von einer Root aus erreichbar ist, sodass der GC es behalten muss. Der Heap füllt sich, der GC läuft immer häufiger, und schließlich erhalten Sie einen OutOfMemoryError.

Die klassischen Ursachen sind alle „Ich habe vergessen loszulassen":

  • Eine static Collection (Cache, Listener-Liste, Map), der Sie immer wieder hinzufügen, aber nie etwas entnehmen. Statische Felder sind GC-Roots, also lebt alles, was sie erreichen, für immer.
  • Listener oder Callbacks, die bei einem langlebigen Objekt registriert und nie abgemeldet wurden.
  • Schlüssel, die lange nach ihrer Notwendigkeit in einer HashMap verbleiben, weil die Map sie noch referenziert.

Die Lösung ist kein Flag — es geht darum, Referenzen loszulassen, wenn man fertig ist (aus der Collection entfernen, den Listener abmelden) oder eine WeakReference-basierte Struktur wie WeakHashMap zu verwenden, damit der GC Einträge zurückfordern kann, sobald nichts anderes mehr auf den Schlüssel zeigt.

Warnung
Java hat keine Destruktoren, und finalize() ist veraltet und unzuverlässig — die JVM kann es spät oder gar nicht ausführen. Um Dateien, Sockets oder andere Nicht-Speicher-Ressourcen deterministisch freizugeben, verwenden Sie try-with-resources und AutoCloseable, nicht den Garbage Collector.

Einen Collector wählen

Die HotSpot JVM wird mit mehreren Collectors ausgeliefert, die unterschiedliche Abwägungen zwischen Durchsatz (geleistete Gesamtarbeit) und Latenz (Länge der Pausen) bieten. Sie wählen einen mit einem JVM-Flag; der Standard seit Java 9 ist G1.

CollectorFlagAm besten für
G1 (Standard)-XX:+UseG1GCAusgeglichene Latenz/Durchsatz, große Heaps
Parallel-XX:+UseParallelGCBatch-Jobs, die reinen Durchsatz bevorzugen
ZGC-XX:+UseZGCSehr große Heaps, Pausen unter einer Millisekunde
Serial-XX:+UseSerialGCKleine Heaps, Einzel-Core oder Container
# Pick a collector and set the heap size at launch:
java -XX:+UseG1GC -Xms256m -Xmx2g MyApp

# Print what the GC is doing, with timestamps:
java -Xlog:gc* MyApp

Ein ausgearbeitetes Beispiel

Das folgende Programm macht das Verhalten des GC beobachtbar. Es fixiert ein Objekt mit einer starken Referenz, hält ein anderes nur durch eine WeakReference, erzeugt eine Welle kurzlebigen Garbage, fragt dann nach einer Collection und berichtet, was überlebt hat und wie sich die Heap-Nutzung verändert hat.

java— editable, runs on the server

Was aus dem Lauf mitgenommen werden sollte:

  • Das schwach referenzierte Objekt gibt vor der Collection true und danach false aus, was beweist, dass eine WeakReference ihr Objekt nicht am Leben hält, sobald keine starke Referenz mehr vorhanden ist.
  • Das stark gehaltene kept-Array gibt survived: true aus, selbst nach System.gc(), weil es von einer GC-Root aus erreichbar ist und der Collector es bewahren muss.
  • Ungefähr 300 MB Garbage wird alloziert (Bytes allocated as garbage: 307200000), dennoch steigt der verwendete Heap nur auf etwa 5 MB — Minor GCs fordern die kurzlebigen Schleifen-Arrays so schnell zurück, wie sie erzeugt werden.
  • Runtime.maxMemory() meldet die Heap-Obergrenze (hier etwa 256 MB), festgelegt durch -Xmx, während totalMemory() - freeMemory() der live genutzte Anteil ist, der bei 3–5 MB bleibt.
  • System.gc() ist nur ein Hinweis, aber auf dieser JVM wird er ausgeführt: der verwendete Heap fällt wieder ab und das unerreichbare schwach referenzierte Objekt wird geleert statt zu verbleiben.

Übung

Übung
Ein Objekt wird nur von einer lokalen Variable referenziert, die gerade aus dem Gültigkeitsbereich gefallen ist, und von nichts sonst. Was macht es für die Garbage Collection geeignet?
Ein Objekt wird nur von einer lokalen Variable referenziert, die gerade aus dem Gültigkeitsbereich gefallen ist, und von nichts sonst. Was macht es für die Garbage Collection geeignet?
Was this page helpful?