Garbage Collection in Java
Die Garbage Collection in Java ist eines der fortgeschrittenen Themen. Kenntnisse über Java-GC helfen uns, die Laufzeitleistung unserer Anwendungen zu optimieren.
Garbage Collection in Java
In Java müssen sich Programmierer nicht darum kümmern, nicht mehr benötigte Objekte zu zerstören. Der Garbage Collector übernimmt diese Aufgabe. Der Garbage Collector in Java ist ein Daemon-Thread, der im Hintergrund ausgeführt wird. Grundsätzlich gibt er den Heap-Speicher frei, indem er nicht erreichbare Objekte zerstört. Nicht erreichbare Objekte sind solche, die von keinem Teil des Programms mehr referenziert werden. Wir können den Garbage Collector für unser Java-Programm über JVM-Optionen auswählen, auf die wir in einem späteren Abschnitt dieses Tutorials eingehen werden.
Wie funktioniert die automatische Garbage Collection in Java?
Die automatische Garbage Collection in Java identifiziert nicht erreichbare Objekte im Heap-Speicher und zerstört sie. Mit zunehmender Objekthäufigkeit steigt jedoch die Zeit für die Garbage Collection, da alle Objekte durchsucht werden müssen. Um die Leistung zu verbessern, wurde die „Generational Garbage Collection“ eingeführt, bei der der Heap in Generationen unterteilt wird: „Young Generation“, „Old Generation“ und „Permanent Generation“. Neue Objekte erstellen wir in der „Young Generation“ und bereinigen sie schnell durch eine „Minor GC“, da viele Objekte kurzlebig sind. Überlebende Objekte wechseln in die „Old Generation“, die mit einer langsameren „Major GC“ gesammelt wird. „Full GC“ reinigt den gesamten Heap. Bis Java 7 enthielt die „Permanent Generation“ Metadaten, die Java 8 entfernt hat.
Java Garbage Collectors
Die JVM bietet tatsächlich vier verschiedene Garbage Collector für die Garbage Collection in Java an, die alle generativ sind. Jeder hat seine eigenen Vor- und Nachteile. Die Wahl des zu verwendenden Garbage Collectors liegt bei uns, und es kann erhebliche Unterschiede in der Durchsatzrate und den Anwendungspausen geben. Alle teilen den verwalteten Heap in verschiedene Segmente auf, basierend auf der Annahme, dass die meisten Objekte im Heap kurzlebig sind und schnell recycelt werden sollten. Die vier Arten von Garbage Collectors in Java sind:
Serial GC
Dies ist der einfachste Garbage Collector, der für Einzelprozessorsysteme und kleine Heap-Größen ausgelegt ist. Er friert alle Anwendungen während des Betriebs ein. Kann mit der JVM-Option -XX:+UseSerialGC aktiviert werden.
Parallel/Throughput GC
Dies ist der Standard-Collector der JVM in JDK 8. Wie der Name schon sagt, verwendet er mehrere Threads, um den Heap-Speicher zu durchsuchen und zu komprimieren. Ein Nachteil dieses Collectors besteht darin, dass er die Anwendungsthreads beim Ausführen von „Minor“ oder „Full GC“ pausiert. Er eignet sich am besten für Anwendungen, die solche Pausen tolerieren können und versucht, die durch den Collector verursachte CPU-Belastung zu optimieren.
Der CMS-Collector
Der CMS-Collector (Concurrent-Mark-Sweep) Algorithmus nutzt mehrere Threads („concurrent“), um den Heap nach ungenutzten Objekten zu durchsuchen und sie zu recyceln („sweep“). Dieser Collector wechselt in den „Stop-The-World“ (STW)-Modus in zwei Fällen:
- Bei der Initialisierung der anfänglichen Markierung von Wurzeln, d. h. Objekten in der „Old Generation“, die von Thread-Einstiegspunkten oder statischen Variablen erreichbar sind
- Wenn die Anwendung den Zustand des Heaps geändert hat, während der Algorithmus parallel lief, und sie gezwungen ist, einige abschließende Änderungen vorzunehmen, um sicherzustellen, dass die richtigen Objekte markiert sind.
Dieser Collector kann auf Förderungsfehler stoßen. Wenn einige Objekte aus der „Young Generation“ in die „Old Generation“ verschoben werden sollen und der Collector nicht genügend Zeit hatte, Platz in der „Old Generation“ zu schaffen, tritt ein Förderungsfehler auf. Um dies zu verhindern, können wir dem „Old Generation“-Bereich mehr Heap-Speicher zur Verfügung stellen oder mehr Hintergrundthreads für den Collector bereitstellen.
G1-Collector
Zuletzt, aber nicht weniger wichtig, ist der Garbage-First Collector, der für Heap-Größen über 4 GB ausgelegt ist. Er teilt den Heap in Regionen auf, die je nach Heap-Größe von 1 MB bis 32 MB reichen. Es gibt eine parallele globale Markierungsphase, um die Lebendigkeit von Objekten im gesamten Heap zu bestimmen. Nach Abschluss der Markierungsphase weiß der G1, welche Regionen größtenteils leer sind. Er sammelt zuerst die nicht erreichbaren Objekte aus diesen Regionen, was in der Regel eine große Menge an freiem Speicherplatz ergibt. Daher sammelt der G1 zuerst diese Regionen (die Müll enthalten), daher der Name Garbage-First. Der G1 verwendet auch ein Pause-Vorhersagemodell, um eine benutzerdefinierte Pausenzeit einzuhalten. Er wählt die Anzahl der zu sammelnden Regionen basierend auf der angegebenen Pausenzeit aus. Der G1-Garbage-Collection-Zyklus umfasst die Phasen wie im Bild dargestellt:
Young-only Phase
Diese Phase umfasst nur die Objekte der „Young Generation“ und befördert sie in die „Old Generation“. Der Übergang zwischen der „Young-only“-Phase und der „Space-Reclamation“-Phase beginnt, wenn die „Old Generation“ bis zu einem bestimmten Schwellenwert belegt ist, d. h. dem „Initiating Heap Occupancy“-Schwellenwert. Zu diesem Zeitpunkt plant der G1 eine Initial-Markierungssammlung der jungen Generation anstelle einer regulären Sammlung der jungen Generation.
Initiale Markierung
Diese Art der Sammlung startet den Markierungsprozess zusätzlich zu einer regulären „Young-only“-Sammlung. Die parallele Markierung bestimmt alle aktuell lebenden Objekte in den Regionen der „Old Generation“. Diese soll für die folgende „Space-Reclamation“-Phase beibehalten werden. Während die Markierung noch nicht vollständig abgeschlossen ist, können reguläre „Young-only“-Sammlungen auftreten. Die Markierung endet mit zwei speziellen Stop-The-World-Pausen: „Remark“ und „Cleanup“.
Remark
Diese Pause finalisiert die Markierung selbst und führt die globale Referenzverarbeitung und das Klassenentladen durch. Zwischen „Remark“ und „Cleanup“ berechnet G1 eine Zusammenfassung der Lebendigkeit der Informationen parallel, die in der „Cleanup“-Pause abgeschlossen und zur Aktualisierung der internen Datenstrukturen verwendet wird.
Cleanup
Diese Pause umfasst auch die vollständig leeren Regionen und bestimmt, ob tatsächlich eine „Space-Reclamation“-Phase folgen wird. Falls eine „Space-Reclamation“-Phase folgt, endet die „Young-only“-Phase mit einer einzigen „Young-only“-Sammlung.
Space-Reclamation-Phase
Diese Phase besteht aus mehreren gemischten Sammlungen – zusätzlich zu den Regionen der „Young Generation“ werden auch lebende Objekte der „Old Generation“ evakuiert. Die „Space-Reclamation“-Phase endet, wenn G1 feststellt, dass das Evakuieren weiterer „Old Generation“-Regionen nicht genügend freien Speicherplatz ergeben würde, um den Aufwand zu rechtfertigen.
Der G1 kann mit dem –XX:+UseG1GC-Flag aktiviert werden. Diese Strategie reduzierte die Wahrscheinlichkeit, dass der Heap erschöpft ist, bevor die Hintergrund-Threads das Scannen nach nicht erreichbaren Objekten abgeschlossen haben. Außerdem komprimiert er den Heap „on-the-go“, was der CMS-Collector nur im STW-Modus tun kann. Java 8 führte eine Optimierung mit dem G1-Collector ein, die als String-Deduplikation bezeichnet wird. Wie wir wissen, belegen die Zeichenarrays, die unsere Strings darstellen, viel Speicherplatz im Heap. Eine neue Optimierung ermöglicht es dem G1-Collector, Strings zu erkennen, die mehrmals im Heap dupliziert wurden, und diese so zu modifizieren, dass sie auf dasselbe interne char[]-Array verweisen, um Mehrfachkopien desselben Strings im Heap zu vermeiden. Wir können das JVM-Argument -XX:+UseStringDeduplication verwenden, um diese Optimierung zu aktivieren. G1 ist der Standard-Garbage-Collector in JDK 9.
Java 8 PermGen und Metaspace
Wie bereits erwähnt, entfernte Java 8 den Permanent Generation-Bereich. Jetzt verwendet die HotSpot-JVM von JDK 8 den nativen Speicher für die Darstellung von Klassenmetadaten. Dieser heißt Metaspace. Die meisten Zuweisungen für die Klassenmetadaten erfolgen außerhalb des nativen Speichers. Es gibt auch eine neue Flagge namens MaxMetaspaceSize, um die Menge an Speicher zu begrenzen. Wenn wir keinen Wert angeben, dimensioniert die JVM die Metaspace-Größe zur Laufzeit je nach Bedarf der ausgeführten Anwendung neu. Die JVM löst die Garbage Collection des Metaspace aus, wenn die Nutzung der Klassenmetadaten das MaxMetaspaceSize-Limit erreicht. Übermäßige Metaspace-Garbage-Collection kann ein Hinweis auf Speicherlecks bei Klassen oder Classloadern oder auf eine unzureichende Dimensionierung für unsere Anwendung sein. Das war es zur Garbage Collection in Java. Ich hoffe, Sie haben ein Verständnis für die verschiedenen Garbage Collector, die wir in Java haben, gewonnen.
Referenzen: Oracle Documentation, G1 GC.