W3docs

Java GC-Algorithmen

Vergleich der wichtigsten Java-Garbage-Collector — Serial, Parallel, G1, ZGC und Shenandoah — mit ihren jeweiligen Kompromissen.

Die JVM befreit Sie davon, Speicher manuell freizugeben: Ein Garbage Collector (GC) läuft im Hintergrund, findet Objekte, die Ihr Programm nicht mehr erreichen kann, und gibt deren Speicherplatz frei. „Der Garbage Collector" ist jedoch kein einheitliches Konzept. Die HotSpot-JVM liefert mehrere Collector mit, die jeweils einen anderen Kompromiss zwischen Durchsatz (wie viel CPU Ihrer Anwendung statt dem GC zugutekommt), Latenz (wie lange die Anwendung pausiert, während der GC arbeitet) und Footprint (wie viel Overhead der Collector selbst verursacht) eingehen.

Die richtige Wahl ist wichtig. Ein Batch-Job, der über Nacht Zahlen verarbeitet, möchte maximalen Durchsatz und kümmert sich nicht um Pausen; ein Handelssystem oder ein Webserver möchte die kürzestmöglichen Pausen, selbst wenn der Gesamtdurchsatz etwas sinkt. Dieses Kapitel vergleicht die produktiven Collector und zeigt, was sie alle gemeinsam haben: Ein Objekt lebt genau so lange, wie es erreichbar ist.

Wie Collector entscheiden, was behalten wird

Jeder HotSpot-Collector beantwortet dieselbe Frage — welche Objekte noch in Verwendung sind — auf dieselbe Weise: Er verfolgt die Erreichbarkeit von einer Menge von GC-Wurzeln (lokale Variablen auf dem Stack, statische Felder, aktive Threads). Jedes von einer Wurzel erreichbare Objekt ist lebendig; alles andere ist Garbage. Gültigkeitsbereich und Alter sind irrelevant; nur die Erreichbarkeit zählt. Einen breiteren Überblick, wie dies zur Laufzeit passt, finden Sie unter Java Garbage Collection und Stack vs Heap.

public class Reachability {
    public static void main(String[] args) {
        String a = new String("kept");      // reachable via local variable 'a'
        String b = new String("dropped");   // reachable via 'b'...
        b = null;                            // ...until now: "dropped" is unreachable
        System.out.println(a);               // 'a' is still a GC root reference
    }
}

In dem Moment, in dem b = null ausgeführt wird, hat das Objekt "dropped" keinen Pfad von einer Wurzel mehr und ist für die Collection freigegeben. Der Collector kann es sofort, viel später oder — wenn das Programm zuerst beendet wird — überhaupt nicht freigeben. Sie rufen nie free auf; Sie hören einfach auf zu referenzieren.

Generationale Heap-Struktur

Die meisten Java-Objekte sterben jung. Collector nutzen dies mit einem generationalen Heap: Neue Objekte landen in der jungen Generation (Eden plus zwei Survivor-Bereiche), und Objekte, die mehrere Collections überleben, werden in die alte Generation befördert. Die kleine, stark mit Garbage gefüllte junge Generation häufig zu sammeln ist günstig; die große alte Generation wird viel seltener gesammelt.

BereichWas hier lebtWie oft gesammelt
EdenFrisch allokierte ObjekteBei jeder Minor GC
Survivor (S0/S1)Objekte, die eine Minor GC überlebt habenBei jeder Minor GC
Old (tenured)Langlebige, beförderte ObjekteMajor / Full GC
MetaspaceKlassen-Metadaten (off-heap)Beim Entladen von Klassen

Eine Minor GC bereinigt die junge Generation und ist schnell; eine Major oder Full GC berührt die alte Generation und ist die Quelle der langen Pausen, vor denen man sich fürchtet.

Die Collector im Vergleich

HotSpot lässt Sie einen Collector mit einem einzigen Flag auswählen, und jeder ist auf ein anderes Ziel ausgerichtet. Sie ändern den Algorithmus selten im Code — Sie legen ihn in der Befehlszeile fest.

java -XX:+UseSerialGC     MyApp   # single-threaded, tiny heaps
java -XX:+UseParallelGC   MyApp   # throughput-oriented, multi-threaded
java -XX:+UseG1GC         MyApp   # balanced, the default since Java 9
java -XX:+UseZGC          MyApp   # sub-millisecond pauses, huge heaps
java -XX:+UseShenandoahGC MyApp   # low pause, concurrent compaction

Die folgende Tabelle ist das Denkmodell, das man im Kopf behalten sollte:

CollectorStärkePausenverhaltenTypischer Einsatz
SerialEinfachste, geringer FootprintStop-the-world, ein ThreadKleine Heaps, Container, CLIs
ParallelHöchster DurchsatzStop-the-world, viele ThreadsBatch / Datenverarbeitung
G1Ausgewogen, vorhersehbarÜberwiegend concurrent, PausenzielAllgemeiner Standard
ZGCSehr niedrige LatenzSub-Millisekunden, concurrentMulti-GB bis TB-Heaps
ShenandoahSehr niedrige LatenzPausen unabhängig von der Heap-GrößeReaktionsfähige Dienste

G1 („Garbage-First") ist ab Java 9 der Standard. Er teilt den Heap in gleich große Regionen auf und sammelt zuerst die Regionen mit dem meisten Garbage, wobei er auf ein von Ihnen mit -XX:MaxGCPauseMillis=200 gesetztes Pausenziel hinarbeitet.

Concurrent vs. Stop-the-world

Die entscheidende Dimension ist, wann die Anwendungsthreads anhalten müssen. Stop-the-world (STW)-Collector (Serial, Parallel) pausieren alle Anwendungsthreads, während sie arbeiten — einfach und leistungsstark, aber die Pause wächst mit dem Heap. Concurrent-Collector (ZGC, Shenandoah und ein Großteil von G1) erledigen den Großteil ihrer Arbeit, während Ihre Threads weiterlaufen, sodass Pausen auch bei Heaps von Gigabytes oder Terabytes kurz bleiben.

# See exactly what the collector is doing and how long it pauses
java -Xlog:gc -XX:+UseG1GC MyApp
# Sample output line:
# [0.412s][info][gc] GC(3) Pause Young (Normal) (G1 Evacuation Pause) 24M->5M(64M) 1.832ms

Diese Log-Zeile ist es wert, entschlüsselt zu werden: 24M->5M(64M) bedeutet, dass der genutzte Heap von 24 MB auf 5 MB von insgesamt 64 MB gesunken ist, und die Anwendung für 1.832ms pausierte. Das Lesen von -Xlog:gc-Ausgaben ist die nützlichste einzelne GC-Tuning-Fähigkeit — messen Sie, bevor Sie ein Flag ändern.

Collection aus dem Code heraus beobachten

Sie können von Java aus keinen spezifischen Algorithmus direkt aufrufen, aber Sie können die Collection beobachten. Eine WeakReference ermöglicht es Ihnen, einen Zeiger zu halten, der sein Ziel nicht am Leben hält, sodass Sie fragen können: „Wurde dieses Objekt bereits gesammelt?" Die Runtime-Klasse meldet die Heap-Nutzung, und System.gc() ist ein Hinweis — niemals ein Befehl — eine Collection jetzt durchzuführen.

import java.lang.ref.WeakReference;

WeakReference<byte[]> ref = new WeakReference<>(new byte[1024]);
System.out.println("Before GC: " + (ref.get() != null)); // true
System.gc();
System.out.println("After GC:  " + (ref.get() != null)); // usually false

Das ausführbare Beispiel unten verbindet diese Teile: Es allokiert eine Welle von Garbage, hält einen Überlebenden erreichbar, beobachtet ein unerreichbares Objekt durch eine schwache Referenz und misst den Heap vor und nach einer Collection.

java— editable, runs on the server

Was man aus dem Durchlauf mitnehmen kann:

  • Schritt 2 gibt true aus: Das beobachtete Objekt ist noch stark erreichbar über die Variable garbage, sodass die WeakReference es lesen kann.
  • Schritt 3 meldet den genutzten Heap nach 300.000 kurzlebigen Allokationen. Die genaue Zahl variiert von Durchlauf zu Durchlauf — eine Minor GC könnte diese Welle bereits mitten in der Schleife großteils bereinigt haben — aber ihre Erstellung ist genau der Young-Generation-Umsatz, für den jeder generationale Collector kostengünstig ausgelegt ist.
  • Schritt 4 gibt true aus und bestätigt, dass das beobachtete Objekt freigegeben wurde, sobald garbage = null es unerreichbar machte und System.gc() eine Collection auslöste — Beweis dafür, dass der Verlust der Erreichbarkeit, nicht das Verlassen des Gültigkeitsbereichs, Speicher freigibt.
  • Schritt 5 gibt true aus: Der survivor, der noch von einer lebendigen lokalen Variable (einer GC-Wurzel) referenziert wird, übersteht die Collection unberührt.
  • Schritt 6 zeigt, wie der genutzte Heap nahe dem Ausgangswert zurückfällt, was zeigt, dass der Collector den freigegebenen Speicher zur Wiederverwendung zurückgibt, anstatt dass das Programm ihn verliert.

Mehr zu den hier verwendeten Referenztypen — stark, soft, schwach und phantom — finden Sie unter Java References.

Übungen

Übung
Welche einzige Eigenschaft bestimmt, ob der Garbage Collector ein Objekt behalten wird, unabhängig davon, welcher Algorithmus verwendet wird?
Welche einzige Eigenschaft bestimmt, ob der Garbage Collector ein Objekt behalten wird, unabhängig davon, welcher Algorithmus verwendet wird?
Was this page helpful?