Garbage Collection
Wer räumt den Speicher auf? Wie die JVM erkennt, welche Objekte niemand mehr braucht, warum die meisten Objekte jung sterben, was eine Stop-the-World-Pause verursacht – und warum es in Java trotzdem Speicherlecks gibt.
In Java legt man Objekte mit new an, aber gibt sie nie selbst frei. Das übernimmt der Garbage Collector (GC): Er findet Objekte, die vom Programm aus nicht mehr erreichbar sind, und gibt ihren Speicher frei. Erreichbar heißt: Es gibt einen Weg von Verweisen, der bei einer lokalen Variable, einem statischen Feld oder einem laufenden Thread beginnt.
Die wichtigste Beobachtung dahinter: Die meisten Objekte sterben jung. Deshalb teilt die JVM den Speicher in Generationen und räumt den jungen Bereich oft und billig auf. Moderne Collectors wie G1 (Standard) und ZGC erledigen den Großteil der Arbeit parallel zum Programm und halten Pausen kurz – bei ZGC unter einer Millisekunde, egal wie groß der Speicher ist.
Was ist Müll?
Müll ist nicht „was nicht mehr gebraucht wird“ – das kann keine Maschine wissen. Müll ist, was nicht mehr erreichbar ist. Der GC beginnt bei den Wurzeln – lokale Variablen auf den Stacks aller Threads, statische Felder, ein paar JVM-interne Verweise – und folgt allen Verweisen. Was er dabei erreicht, lebt. Der Rest wird freigegeben. Probier es:
Der Vergleich zeigt den Unterschied der beiden großen Verfahren. Referenzzählung zählt für jedes Objekt, wie viele Verweise auf es zeigen, und gibt es bei null sofort frei. Das ist einfach und vorhersehbar – aber zwei Objekte, die aufeinander zeigen, halten sich gegenseitig ewig am Leben. Tracing fragt nur: Komme ich von den Wurzeln aus hin? Zyklen sind dann kein Problem. Die JVM benutzt ausschließlich Tracing.
Die meisten Objekte sterben jung
Ein Iterator in einer Schleife, ein String beim Zusammenbauen einer Log-Zeile, ein DTO für eine einzige HTTP-Antwort – die überwiegende Mehrheit aller Objekte lebt nur Millisekunden. Das ist die Generationenhypothese, und sie stimmt bei fast allen Programmen. Die JVM nutzt das aus:
- Neue Objekte entstehen im Eden. Das ist extrem billig: Jeder Thread hat seinen eigenen Abschnitt und schiebt nur einen Zeiger weiter – ein paar Maschinenbefehle.
- Ist Eden voll, gibt es eine kleine Sammlung (Minor GC): Die wenigen Überlebenden werden in einen Survivor-Bereich kopiert, der Rest von Eden ist mit einem Schlag frei. Der Aufwand hängt nur von den Überlebenden ab – nicht vom Müll.
- Wer mehrere kleine Sammlungen überlebt, wird in die alte Generation befördert. Die wird selten und gründlicher aufgeräumt.
Die Collectors und ihre Pausen
Während der GC Objekte verschiebt, darf das Programm sie nicht gleichzeitig benutzen. Ältere Collectors halten deshalb alle Threads an – eine Stop-the-World-Pause. Moderne Collectors erledigen fast alles nebenläufig, während das Programm weiterläuft, und brauchen nur noch winzige Pausen zum Abstimmen. Der Preis dafür ist etwas mehr Rechenzeit und Speicher.
| Collector | Stärke | Typisch für |
|---|---|---|
| Serial | ein Thread, minimaler Aufwand | kleine Programme, Container mit einem Kern |
| Parallel | höchster Durchsatz, längere Pausen | Batch-Verarbeitung, wo nur die Gesamtzeit zählt |
| G1 | ausgewogen: guter Durchsatz, planbare Pausen. Teilt den Heap in viele Regionen und räumt zuerst die mit dem meisten Müll | Standard seit Java 9 – für die meisten Anwendungen richtig |
| ZGC | Pausen unter einer Millisekunde, auch bei Terabyte-Heaps; seit Java 21 mit Generationen | Dienste mit strengen Antwortzeiten, große Heaps |
| Shenandoah | ebenfalls fast pausenlos, aus dem OpenJDK-Umfeld von Red Hat | wie ZGC |
Speicherlecks in Java
Ein Garbage Collector verhindert, dass vergessene Objekte Speicher belegen. Er kann aber nicht verhindern, dass Objekte erreichbar bleiben, die niemand mehr braucht. Genau das ist ein Speicherleck in Java – und im Widget oben mit „Speicherleck einschalten“ zu sehen: Die alte Generation läuft voll, große Sammlungen werden immer häufiger und bringen immer weniger, bis ein OutOfMemoryError kommt.
Wachsende Sammlungen
Eine statische Map als Cache, in die nur hineingelegt, aber nie etwas entfernt wird. Der Klassiker schlechthin.
Vergessene Listener
Ein Objekt meldet sich als Beobachter an, aber nie ab. Der Beobachtete hält es für immer fest.
ThreadLocal im Pool
Threads in einem Pool leben ewig – und mit ihnen alles, was in ihren ThreadLocals liegt, wenn man es nicht mit remove() entfernt.
Für Entwickler übersetzt
# Collector wählen und Heap festlegen
java -XX:+UseZGC -Xms4g -Xmx4g -jar app.jar
# GC-Protokoll mitschreiben
java -Xlog:gc*:file=gc.log -jar app.jar
# Bei OutOfMemoryError automatisch ein Speicherabbild schreiben
java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp -jar app.jar
# Laufende JVM untersuchen
jcmd <pid> GC.heap_info
jcmd <pid> GC.class_histogram | head -20// Leck: wächst bei jeder Anfrage, wird nie kleiner
private static final Map<String, Bericht> CACHE = new HashMap<>();
// Besser: begrenzter Cache mit Ablaufzeit, z. B. mit der Bibliothek Caffeine
private final Cache<String, Bericht> cache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(Duration.ofMinutes(10))
.build();| Werkzeug | Wofür |
|---|---|
| GC-Log | Wie oft, wie lange, wie viel freigegeben? Der erste Blick bei Performance-Problemen. |
| Heap-Dump + Eclipse MAT | Wer hält die vielen Objekte fest? Der „Dominator Tree“ zeigt den Verursacher eines Lecks meist sofort. |
| Java Flight Recorder | Laufende Aufzeichnung mit wenig Overhead: Allokationen, GC, Sperren, heiße Methoden. Auswertung in JDK Mission Control. |
| try-with-resources | Der GC räumt Speicher, nicht Dateien, Sockets oder Datenbankverbindungen. finalize() ist seit Java 18 zum Entfernen vorgesehen und war nie verlässlich. |
Häufige Irrtümer
System.gc() hilft.“ – Es ist nur ein Hinweis, löst oft eine teure volle Sammlung aus und stört die Heuristiken des Collectors. In normalem Code hat es nichts zu suchen.null setzen hilft dem GC.“ – Bei lokalen Variablen fast nie – der JIT weiß ohnehin, ab wann eine Variable nicht mehr benutzt wird. Sinnvoll ist es nur bei Feldern langlebiger Objekte, etwa in selbst gebauten Sammlungen.