1. Einführung in den Garbage Collector (GC)
Wenn Sie jemals in C oder C++ programmiert haben, sind Sie sicher auf die Notwendigkeit gestoßen, Speicher manuell mit free() oder delete freizugeben. In Java ist das viel einfacher: Sie erstellen ein Objekt mit new, und müssen es nicht löschen – darum kümmert sich ein spezieller „Hausmeister“, der Garbage Collector (Garbage Collector, GC).
Der GC ist ein Teil der JVM, der Speicher, der von Objekten ohne weitere Referenzen belegt wird, automatisch freigibt. Dank ihm müssen Java‑Entwickler nicht befürchten, das Freigeben von Speicher zu vergessen (Speicherleck) oder umgekehrt versehentlich ein noch benötigtes Objekt zu löschen (und einen Crash zu kassieren).
Aber wie jeder Hausmeister ist der GC nicht perfekt: Er kann sich manchmal im ungünstigsten Moment einmischen und eine „Grundreinigung“ ansetzen (Stop-the-World), oder nicht so schnell arbeiten, wie man es gerne hätte. Deshalb gibt es in der JVM mehrere verschiedene Implementierungen des Garbage Collectors – und die richtige Wahl kann die Performance einer Anwendung spürbar beeinflussen.
Grundtypen der Garbage Collector
Serial GC
- Serial GC – der einfachste und älteste Garbage Collector.
- Arbeitet in einem einzigen Thread.
- Hält alle anderen Threads während der Bereinigung an (Stop-the-World).
- Gut für kleine Anwendungen ohne intensive Multithreading‑Aktivität.
- Wird mit folgendem Flag aktiviert: -XX:+UseSerialGC
Parallel GC
- Parallel GC (auch „Throughput Collector“).
- Nutzt mehrere Threads für die Bereinigung.
- Auf maximale Durchsatzrate ausgerichtet.
- Führt weiterhin Bereinigungen mit Stop-the-World-Pausen aus, ist aber schneller als Serial.
- Geeignet für Server‑Anwendungen, bei denen kleine Pausen unkritisch sind.
- Wird mit folgendem Flag aktiviert: -XX:+UseParallelGC
CMS (Concurrent Mark Sweep)
- CMS – veraltet, aber lange populär, minimiert Pausen.
- Arbeitet teilweise parallel zur Anwendung und verkürzt so die Anhaltezeit.
- Komplexer in der Konfiguration, mit Overhead.
- Seit Java 9 als deprecated markiert.
- Wird mit folgendem Flag aktiviert: -XX:+UseConcMarkSweepGC
G1 (Garbage First)
- G1 GC – moderner Standard‑Collector (seit Java 9).
- Balanciert zwischen minimalen Pausen und Performance.
- Teilt den Heap in viele kleine Regionen (Regionenmodell).
- Kann Müll selektiv nach Regionen sammeln, ohne den gesamten Heap anzufassen.
- Ermöglicht eine Ziel‑Maximalpause vorzugeben, z. B. -XX:MaxGCPauseMillis=200.
- Aktivierungs‑Flag: -XX:+UseG1GC (meist nicht nötig, da G1 standardmäßig aktiv ist).
ZGC und Shenandoah
- ZGC und Shenandoah – moderne Low‑Latency‑Collector.
- Ziel – minimale Pausen (Millisekunden), selbst auf riesigen Heaps (bis hin zu Terabytes).
- Arbeiten nahezu vollständig parallel zur Anwendung.
- Erfordern Java 11+ (ZGC) bzw. Java 12+ (Shenandoah).
- Geeignet für latenzkritische Systeme (Börsen, Fintech, Echtzeit‑Analytik).
- Aktivierungs‑Flags: -XX:+UseZGC oder -XX:+UseShenandoahGC
3. Funktionsprinzipien moderner GC
Junge und alte Generation (Young/Old Generation)
Die JVM teilt den Heap in zwei große Bereiche:
Junge Generation (Young Generation): Hier landen alle neuen Objekte. Die Bereinigung erfolgt hier häufig und schnell (Minor GC).
Alte Generation (Old Generation, Tenured): Hierhin wandern Objekte, die mehrere Bereinigungen in der jungen Generation „überlebt“ haben. Die Bereinigung erfolgt seltener, dauert aber länger (Major/Full GC).
Warum? Die meisten Objekte in Java leben nur sehr kurz (z. B. temporäre Strings, Collections innerhalb einer Methode). Deshalb kann man die junge Generation schnell und oft bereinigen, ohne die alte anzutasten.
Minor GC
- Bereinigt nur die junge Generation.
- Schnell, mit kurzer Pause.
- Betroffen sind keine alten Objekte.
Major (Full) GC
- Bereinigt den gesamten Heap (jung und alt).
- Kann viel Zeit beanspruchen (Sekunden und mehr bei großen Heaps).
- Geht normalerweise mit einer langen Anwendungspause einher.
Wie entscheidet der GC, welche Objekte gelöscht werden?
Der GC sucht „lebende“ Objekte ausgehend von den Wurzelreferenzen (root set): lokale Variablen in Thread‑Stacks, statische Felder, Methodenparameter usw. Alles, was erreichbar ist, gilt als lebendig. Der Rest ist Müll.
4. Vergleich moderner Collector: G1, ZGC, Shenandoah
Schauen wir uns an, worin sich die modernsten und populärsten GC unterscheiden. Dazu eine anschauliche Tabelle:
| Collector | Hauptziel | Speichermodell | Minimale Pausen | Skalierbarkeit | Unterstützung | Wann verwenden |
|---|---|---|---|---|---|---|
| G1 | Balance aus Pausen und Geschwindigkeit | Regionen | ~10–200 ms | Bis zu mehreren hundert GB | Java 9+ (Standard) | Die meisten Server‑Anwendungen |
| ZGC | Minimale Pause | Regionen, „farbige Markierungen“ | <10 ms | Bis zu mehreren TB | Java 11+ | Echtzeit, latenzkritisch |
| Shenandoah | Minimale Pause | Regionen, „farbige Markierungen“ | <10 ms | Bis zu mehreren TB | Java 12+ (RedHat) | Echtzeit, latenzkritisch |
G1 GC: Garbage First
- Teilt den Heap in viele Regionen (typisch 1–32 MB je Region).
- Wählt bei der Bereinigung diejenigen Regionen aus, die am meisten Müll enthalten („garbage first“).
- Kann nur einen Teil des Heaps sammeln statt alles auf einmal.
- Erlaubt eine Ziel‑Pause vorzugeben: -XX:MaxGCPauseMillis=200.
- Geeignet für den Kompromiss zwischen Geschwindigkeit und Pausen; seit Java 9 Standard.
Beispiel zum Aktivieren (falls deaktiviert):
java -XX:+UseG1GC -jar myapp.jar
ZGC: Z Garbage Collector
- In Java 11 experimentell, ab Java 15 stabil.
- Hält die Anwendung kaum an: Pausen sind meist <10 ms, selbst bei 1–2 TB Heap.
- Verwendet „farbige Markierungen“ (Coloring) und spezielle Zeiger.
- Erfordert eine 64‑Bit‑JVM; funktioniert nicht auf 32‑Bit‑Systemen.
- Unterstützt auf Linux, macOS, Windows.
Beispiel zum Aktivieren:
java -XX:+UseZGC -jar myapp.jar
Shenandoah
- Von RedHat entwickelt; Ziele ähnlich wie bei ZGC.
- Minimale Pausen, arbeitet weitgehend parallel zur Anwendung.
- Unterstützung für Linux und Windows; Teil einiger OpenJDK‑Builds.
- Verwendet ähnliche Techniken, aber andere interne Algorithmen.
Beispiel zum Aktivieren:
java -XX:+UseShenandoahGC -jar myapp.jar
Visueller Vergleich
graph TD
A[Junge Generation] -->|Minor GC| B[Alte Generation]
B -->|Major GC| C[GC‑Pause]
D[G1: Regionen] --> E[Ausgewählte Regionen]
F[ZGC/Shenandoah: Regionen] --> G[Parallele Bereinigung]
5. Praxis: wie GC erkennen und ändern
Wie herausfinden, welcher GC verwendet wird?
- JVM‑Logs: Starten Sie die Anwendung mit -Xlog:gc* (Java 9+) oder -verbose:gc (bis Java 8). In den Logs sehen Sie, welcher GC aktiv ist und wie oft Pausen auftreten.
- jcmd: Führen Sie aus:
wobei <pid> die Java‑Prozess‑ID ist.jcmd <pid> VM.flags - jvisualvm: Im Bereich „Monitoring“ können Sie den GC‑Typ sehen.
Wie ändern Sie den GC für Ihre Anwendung?
Fügen Sie beim Start der Java‑Anwendung das gewünschte Flag hinzu:
G1 GC (Standard, kann explizit angegeben werden):
java -XX:+UseG1GC -jar myapp.jar
ZGC:
java -XX:+UseZGC -jar myapp.jar
Shenandoah:
java -XX:+UseShenandoahGC -jar myapp.jar
Wie legen Sie Heap‑Größe und Pausen fest?
- Maximale Heap‑Größe: -Xmx2G
- Minimale Heap‑Größe: -Xms512M
- Für G1: gewünschte Pause – -XX:MaxGCPauseMillis=200
Beispiel für einen vollständigen Start:
java -Xms512M -Xmx2G -XX:+UseG1GC -XX:MaxGCPauseMillis=100 -jar myapp.jar
6. Besonderheiten der GC‑Wahl für verschiedene Einsatzfälle
Wann G1 wählen
- Für die meisten Server‑ und Desktop‑Anwendungen – eine ausgezeichnete Standardwahl.
- Funktioniert gut auf Heaps von hunderten Megabyte bis zu mehreren hundert GB.
- Balanciert zwischen Geschwindigkeit und Pausen.
Wann ZGC oder Shenandoah wählen
- Wenn die Anwendung empfindlich auf Latenzen reagiert (latenzkritisch: Börsen, Online‑Spiele, Echtzeit‑Analytik).
- Wenn der Heap riesig ist (hunderte Gigabyte und mehr).
- Wenn nur minimale Pausen (Millisekunden) zulässig sind.
- Erfordert Java 11+ (ZGC) bzw. Java 12+ (Shenandoah).
Wann Parallel GC ausreicht
- Für kleine Anwendungen, bei denen maximale Durchsatzrate wichtig ist und Pausen unkritisch sind.
- Für Batch‑Verarbeitung, wo eine Anhaltezeit durch Full GC „verkraftbar“ ist.
7. Beispiel: GC‑Verhalten in einer einfachen Anwendung
Eine kleine Anwendung, die viele kurzlebige Objekte erzeugt (Simulation der Auftragsverarbeitung):
public class GCSimulator {
public static void main(String[] args) {
while (true) {
// Wir erzeugen in jeder Runde 100 000 Objekte
for (int i = 0; i < 100_000; i++) {
String s = new String("Order-" + i);
}
// Kurz schlafen
try { Thread.sleep(100); } catch (InterruptedException e) {}
}
}
}
Starten Sie sie mit verschiedenen GC und schauen Sie in die Logs:
java -Xmx256M -XX:+UseG1GC -Xlog:gc* GCSimulator
java -Xmx256M -XX:+UseZGC -Xlog:gc* GCSimulator
Was werden Sie sehen?
G1 wird häufige, aber kurze Pausen machen. ZGC/Shenandoah – noch kürzere Pausen, dafür eventuell häufiger. Parallel GC – längere, aber seltenere Pausen.
8. Typische Fehler und Fallstricke im Umgang mit dem GC
Fehler Nr. 1: Zu erwarten, dass der GC alle Speicherprobleme löst. Der GC ist kein Zauberstab. Wenn Sie Referenzen auf nicht mehr benötigte Objekte halten, hilft kein GC – es entsteht ein Speicherleck.
Fehler Nr. 2: Erzwungener Aufruf von System.gc(). Die JVM weiß selbst am besten, wann bereinigt werden sollte. Ein erzwungener GC kann eine lange Pause verursachen und die Performance verschlechtern.
Fehler Nr. 3: GC‑Logs ignorieren. Wenn Sie die GC‑Logs nicht beobachten, merken Sie womöglich nicht, dass Ihre Anwendung regelmäßig bei Full GC „hängenbleibt“.
Fehler Nr. 4: Veraltete GC verwenden. Zum Beispiel wird CMS nicht mehr weiterentwickelt. Besser auf G1 oder moderne Low‑Latency‑Collector wechseln.
Fehler Nr. 5: Falsche GC‑Wahl für den Anwendungsfall. Wenn Sie eine latenzkritische Anwendung haben und Parallel GC verwenden – rechnen Sie mit langen Pausen. Geht es um Batch‑Verarbeitung und Sie schalten ZGC ein – entstehen unnötige Overheads.
GO TO FULL VERSION