Der Z Garbage Collector, kurz ZGC, stellt eine der wichtigsten Fortschritte im Bereich des Java-Speichermanagements dar. Seine Entwicklung zielte darauf ab, die Stop-the-World-Pausen drastisch zu reduzieren, die bei vielen traditionellen Garbage-Collector-Algorithmen auftreten. ZGC erreicht exzellente Leistung durch eine fast vollständig nebenläufige Abarbeitung verschiedener Garbage-Collector-Phasen mit Pausenzeiten von unter einer Millisekunde. Dieser Text gewährt einen tiefen Einblick in die Architektur von ZGC, erklärt die entscheidenden Konzepte hinter seiner Funktionsweise, und zeigt zudem, warum er in modernen Java-Umgebungen zunehmend an Bedeutung gewinnt.Eine der grundlegenden Herausforderungen beim Garbage Collecting ist es, Objekte im Speicher zu verwalten, sie bei Bedarf zu verschieben oder zu entfernen und gleichzeitig sicherzustellen, dass die Anwendung immer korrekte Referenzen auf die Objekte besitzt.
Klassische Garbage Collector pausieren häufig die Anwendung, während sie den Speicher bereinigen. Dies führt zu spürbaren Verzögerungen, die besonders in Systemen mit hohem Durchsatz oder geringer Latenz problematisch sind. Der ZGC setzt genau hier an und verfolgt einen radikal anderen Ansatz, der fast alle wesentlichen Phasen des Garbage Collectings parallel zur Anwendung ausführt.Das Herzstück von ZGC ist die Fähigkeit, nahezu alle Garbage-Collector-Phasen nebenläufig durchzuführen. Während herkömmliche Collector oft auf pausenintensive Mark- und Kompaktierungsphasen setzen, führt ZGC Markierung, Verarbeitung von Referenzen, Auswahl von Relokationsbereichen, JNI-Schleifen sowie Thread-Stack-Scans parallel aus.
Synchronisationspunkte, also kurze Pausenphasen, dienen vor allem der Koordination, ohne dass die Dauer der Pausen sich durch die Menge des belegten Speichers merklich erhöht. Diese stete, niedrige Pausenzeit macht ZGC besonders attraktiv für Anwendungen mit hohen Anforderungen an Skalierbarkeit und Reaktionsfähigkeit.Der ZGC-Garbage-Collector-Zyklus durchläuft in jedem Sammlungsdurchgang mehrere Phasen, die sich abwechselnd pausierende Synchronisationspunkte und lange, nebenläufig ausgeführte Arbeiten verbinden. Zu Beginn steht das kurze „Pause Mark Start“, in dem die Markierung initialisiert wird. Darauf folgt die „Concurrent Mark“-Phase, in der nebenläufig und ohne Unterbrechung der Anwendung der gesamte Objektgraph durchwandert und markiert wird.
Nach der Markierung schließt sich ein kleiner „Pause Mark End“ an und anschließend die „Concurrent Prepare for Relocation“, in der für das spätere Verschieben von Objekten vorbereitet wird. Schließlich folgt „Pause Relocate Start“, das signalisiert, dass gleich Objekte verschoben werden, und die wichtige Phase „Concurrent Relocate“, in der die tatsächliche Kompaktierung und Verschiebung erfolgt. Mit dem Abschluss des Zyklus sind wieder kurze Pausen verbunden, die aber kaum ins Gewicht fallen. Diese sehr feingranulare Steuerung garantiert die Verbindung aus niedrigen Latenzen und stabiler Speichernutzung.Ein besonders originelles Element der ZGC-Architektur sind die sogenannten farbigen Zeiger (Colored Pointers).
Um sicherzustellen, dass der Java-Anwendung immer die aktuelle, korrekte Adresse eines Objekts vorliegt – selbst wenn dieses gerade parallel im Speicher verschoben wird – kodiert ZGC zusätzliche Meta-Informationen in jeden 64-Bit-Zeiger. Von den insgesamt 64 Bits reserviert ZGC 22 Bits für diesen Zweck, darunter vier Bits, die aktuell aktiv als farbige Flags genutzt werden. Diese Flags geben beispielsweise an, ob ein Objekt finalisierbar ist, ob ein Zeiger bereits auf ein Objekt außerhalb des aktuellen Relokationssets verweist oder ob ein Objekt durch die GC-Markierung als erreichbar markiert wurde.Farbliche Kennzeichnungen können dabei als „gute“ oder „schlechte“ Farben interpretiert werden, abhängig vom jeweiligen GC-Zyklus. Während die Anwendung selbst nichts von diesen Farben mitbekommt, ermöglichen sie der JVM durch sogenannte Load Barriers den Zugriff auf die korrekten Objektadressen.
Load Barriers sind Codesegmente, die in jede Stellen im Klassencode eingefügt werden, an denen ein Objekt aus dem Heap gelesen wird. Dadurch kann bei Laden eines Zeigers überprüft und gegebenenfalls korrigiert werden, ob die aktuelle Farbkennung korrekt ist. Ist dies nicht der Fall, kann der Load Barrier die Adresse reparieren, auch indem das Objekt selbst nochmal bewegt wird. Die Optimierung besteht darin, dass dieser Vorgang im Normalfall sehr schnell ist, wenn sich Zeiger in ihrer „guten“ Farbe befinden, um die Anwendung nicht unnötig zu bremsen.Die Art und Weise, wie ZGC das Verschieben von Objekten handhabt, ist eng verbunden mit dem Konzept des Heap-Multi-Mappings.
Da Objekte im Speicher physisch verlegt werden können, muss der virtuelle Speicher des Prozesses mehrere, alternative Adressbereiche bereitstellen, die jeweils auf unterschiedliche Farbzustände der Zeiger verweisen. Konkret werden Objekte zu jedem Zeitpunkt in drei Ansichtsmöglichkeiten virtuell abgebildet, ohne dass drei Kopien tatsächlich vorhanden sind. Dies ermöglicht es Load Barriers, auch bei wechselnden Speicherorten immer die aktuell korrekte Speicheradresse zu liefern. Ein Nebeneffekt dieses Multi-Mappings ist, dass Betriebssystem-Tools den Speicherverbrauch von Java bei ZGC manchmal höher anzeigen, als tatsächlich physisch belegt wird, da mehrere virtuelle Abbildungen nebeneinander existieren.Eine weitere Schlüsselkomponente von ZGC ist die Einteilung des Heaps in dynamische Regionen, sogenannte ZPages.
Diese Regionen beginnen klein, mittig oder auch groß, wobei die Größen je nach Heap-Konfiguration und Objektgrößen unterschiedlich ausfallen. Die Regionseinteilung ist nicht starr, sondern veränderbar basierend auf der Speicherbelegung und Verteilung lebender Objekte. Durch die Segmentierung ist es möglich, Regionen komplett freizugeben, wenn keine erreichbaren Objekte mehr enthalten sind, was die Effizienz der Speicherbereinigung steigert. Außerdem lassen sich so zusammengehörende Objekte gruppieren, was zur besseren Cache-Ausnutzung und geringeren Fragmentierung führt.Die kleinste Regionengröße beträgt 2 MB.
Objekte, die kleiner als ein Achtel der kleinen Region sind, also etwa maximal 256 KB groß, werden hier einsortiert. Mittlere Regionen sind abhängig von der gesamten Heapgröße und variieren zwischen 4 MB bei kleinen Heaps bis zu 32 MB bei größeren Heaps ab 1 GB. Objekte, die weniger als 12,5 % der jeweiligen mittleren Region ausmachen, werden an diesen Orten verwaltet. Große Regionen schließlich sind exklusiv für „riesige“ Objekte reserviert, die nicht mehr in mittelgroße Regionen passen. Der Bereich solcher „humongous“ Objekte wird immer aufgerundet auf das nächsthöhere Vielfache von 2 MB, um eine optimale Anpassung an die Regionengröße zu gewährleisten.
Kompaktion und Relokation sind essenzielle Techniken, um den Heap übersichtlich und wenig fragmentiert zu halten, gerade weil nicht alle Objekte exakt gleichzeitig verfallen. Interne Heuristiken bewerten die Auslastung von Regionen und bestimmen, wann Objekte aus Regionen kopiert werden, die zum Großteil aus unerreichbaren Objekten bestehen. Anschließend kann die Region komplett freigegeben werden, was den Speicherverbrauch effektiv reduziert. ZGC nutzt dabei zwei unterschiedliche Richtungen der Relokation – „Not-in-place“ und „In-place“. Bei „Not-in-place“-Relokation werden freie Regionen verwendet, um Objekte in eine andere Region zu verschieben, was die bevorzugte und effizientere Methode ist.
Sollte kein freier Bereich vorhanden sein, wird in der „In-place“-Relokation innerhalb der Zielregion kompakt gearbeitet, um Platz zu schaffen. Diese limitiert bei der Parallelisierung auf einen einzelnen Thread und erfordert eventuell größere Heaps, um ihre Anwendung zu minimieren.Die Kombination aus farbigen Zeigern, Load Barriers, Heap-Multi-Mapping und der feingesteuerten Regionseinordnung macht ZGC zu einem äußerst modernen, auf niedrige Pausen optimierten Garbage Collector. Besonders für Anwendungen, bei denen Reaktionszeit und Durchsatz essenziell sind, ist ZGC eine hervorragende Wahl. Insbesondere bei sehr großen Heaps oder Systemen, in denen langlebige Objekte über diverse Zyklusphasen bestehen bleiben, zeigen sich die Vorteile von ZGC deutlich.
Neben der technischen Architektur ist auch der Entwicklungsprozess von ZGC bemerkenswert. Der Collector ist inzwischen in OpenJDK integriert und wird kontinuierlich weiterentwickelt. Seit der Version JDK 16 bringt ZGC offizielle Stabilität und aktiv genutzte neue Features wie die Kompaktierung. Die Entwickler arbeiten ständig an Verbesserungen in der Performance, Speicherverwaltung und an der Reduzierung von Overhead und Komplexität. Die Open-Source-Natur trägt zudem dazu bei, dass Unternehmen und Entwickler den Garbage Collector an ihre Bedürfnisse anpassen können.
Für Entwickler, die ZGC einsetzen möchten, ist wichtig zu wissen, dass die Konfiguration über JVM-Optionen unkompliziert ist und dass viele der neuen JVM-Versionen ZGC als Wahl ermöglichen. Durch die beinahe konstant niedrigen Pausenzeiten kann ZGC in Microservices, Backend-Systemen, Echtzeitanwendungen oder Cloud-Native Umgebungen die Performance erheblich verbessern. Die Selbstanpassung der Regionsgrößen und die geschickte Einbindung in moderne Just-in-Time-Compiler wie C2 sind weitere Faktoren, die für eine breite Einsetzbarkeit sprechen.Zusammenfassend ist der Z Garbage Collector eine bedeutende Innovation in der Java-Landschaft. Seine Architektur überzeugt durch nebenläufige, feingranulare Phasen, die durch ausgeklügelte Mechanismen wie farbige Zeiger und Load Barriers getragen werden.
Die dynamische Heap-Segmentierung sorgt dafür, dass Speicher effizient genutzt und Fragmentierung vermieden wird. Kompaktierungsstrategien garantieren eine nachhaltige Speicherverwaltung, selbst bei wechselnden Objektlebenszyklen. Diese Kombination macht ZGC zu einem leistungsfähigen Werkzeug für moderne Anwendungen, die auf geringe Latenz und hohe Skalierbarkeit angewiesen sind. Entwickler und Architekten, die den ZGC verstehen und nutzen, können so das Maximum aus ihrem Java-Ökosystem herausholen und zukunftssichere Softwarelösungen gestalten.