Garbage First (G1) ist eine neue Implementierung der Speicherverwaltung einer Java Virtual Maschine. Der G1 ist entwickelt worden um das Speicherverhalten einer JVM mit großem Heap (groß bedeutet in diesem Fall 6 GB oder mehr) zu verbessern. Des Weiteren wurde Wert darauf gelegt die Einstellungsmöglichkeiten zu verringern, da der bisherige Concurrent Mark Sweep zu viele Möglichkeiten hatte und eine Einstellungsänderung unklare Auswirkungen auf dessen Verhalten hat.

Struktur des Heaps

Der G1 verwaltet den Heap indem er ihn in n gleichgroße Teilbereiche zerlegt. Diese Teilbereiche können zwischen 1MB und 64MB groß sein. Die Teilbereiche sind in folgende 4 Kategorien unterteilt:

  • leerer Bereich (hier sind keine Objekte gespeichert)
  • Eden Bereich (hier sind neu allokierte Objekte gespeichert)
  • Survivor Bereich (hier sind etwas ältere Objekte gespeichert)
  • Old Bereich (hier sind die ältesten Objekte gespeichert)

Das “Alter” von Objekte wird über deren Verweildauer im Heap definiert. Bild (1) zeigt eine schematisch Heapaufteilung, in der alle 4 Kategorien zu sehen sind (leere Bereiche sind grau dargestellt, zur Vereinfachung der Grafik sind diese zusammenhängend dargestellt und nicht als einzelne Blöcke gekennzeichnet).

G1_Heap_Structure

(1) Beispielhafte Heapstruktur des Garbage Collectors G1

Im Default Modus wird der G1 versuchen den Heap in ca. 2000 Bereiche zu unterteilen. Wenn ein Bereich leer ist, kann er vom G1 zu jedem der 3 anderen Bereiche umgewandelt werden. Durch diese Flexibilisierung wirkt man dem starren Konstrukt der bisherigen Garbage Collectoren entgegen (bei diesen ist es notwendig die Größen der Heapbereiche bereits zum Start der JVM zu definieren und es ist zur Laufzeit nicht möglich diese zu ändern). Durch dieses starre Konstrukt haben Anwendung deren Speicheranforderung nicht homogen über die Laufzeit verteilt sind sichtlich Probleme. Beim G1 passen sich die Heapbereiche dem Bedarf zur Laufzeit an.

Speicherallokation

Neue Objekte werden immer in eine Region der Kategorie Eden allokiert. Dazu hat jede Region einen BumpPointer, der angibt wo die nächste freie Speicherstelle in der Region ist. Jeder Thread hat eine eigene Region zugewiesen um Objekte zu allokieren. Dadurch werden Multithreading Probleme relativ einfach umgangen.

Da der G1 während einer Garbage Collection einzelne Regionen betrachtet, ist es notwendig, Informationen bzw. Zeiger die zwischen Regionen sind, gesondert zu verwalten. Solche Zeiger entstehen beispielsweise wenn ein altes Objekt (i.d.R. in einem Old Bereich gespeichert) ein Zeigen auf ein neu erstelltest Objekt (im Eden Bereich gespeichert) zugewiesen bekommt. Um diese Zeiger zwischen Regionen zu verwalten hat jede Region ein Remember Set. Dieses Remember Set beinhaltet Zeiger auf Objekte die wiederum Zeiger auf Objekte in der Region des Remember Sets haben, d.h. das Remember Set beinhaltet alle Referenzen die von anderen Regionen auf diese Region zeigen. Um das Remember Set aktuell zu halten, wird eine Schreibbarriere (write-barrier) beim allokieren neuer Objekte hinzugefügt. In dieser Schreibbarriere wird geprüft ob das this-Objekt und das neu allokierte Objekt in der gleichen Region sind oder nicht. Sind beide Objekte nicht in der gleichen Region, so wird ein Eintrag in das Remember Set der Region des neu allokierten Objektes hinzugefügt. Es wird nicht direkt ein Eintrag vorgenommen, sondern es wird ein Arbeitsauftrag zum Erstellen des Eintrages erstellt. Dieser Arbeitsauftrag wird in einer Queue zwischengespeichert und von einem niedrig priorisiertem Thread im Hintergrund abgearbeitet (dadurch wird die Allokationsgeschwindigkeit reduziert). Das nachfolgende Codebeispiel zeigt exemplarisch wie ein Eintrag ins Remember Set vorgenommen wird (unter der Voraussetzung dass this und new ObjectB() in verschiedenen Regionen gespeichert sind).

G1_Update_RS

Die Abbildung zeigt das Update des Remember Sets wenn die Methode loadObjectB() ausgeführt wird

Speicherbereinigung

Garbage First – hier ist der Name Programm. Im Wesentlichen ist das Ziel des Garbage Collectors Regionen zu bereinigen, in denen möglichst viel Müll ist bzw. möglichst viele unreferenzierte Objekte. Das bedeutet aber das der G1 kontextabhängig entscheidet welche Speicherregionen bereinigt werden und welche nicht. Der G1 sucht nun aus allen Regionen jene heraus, welche den meisten Speicher freigeben. Diese Regionen speichert der G1 in einem Collection Set, d.h. im Collection Set sind alle Regionen die im nächsten Garbage Collection Zyklus bearbeitet werden. Dabei spielt deren Anzahl keine Bedeutung, da die Regionen und deren Remember Set vereinigt werden, d.h. für den Collector spielt es keine Rolle in welcher Region ein Objekt ist. Dieses dynamische Zusammenstellen des für den Collector relevanten Bereiches ist eine grundlegender Unterschied zu den bisherigen Garbage Collection Strategien. In bisherigen Strategien war der Speicher in 2 oder 3 Regionen unterteilt und der Garbage Collector hat immer eine gesamte Region bereinigt.

Der G1 hat zwei verschiedene Modi mit denen er den Speicher bereinigt. Zum einen den Modus Young und zum anderen den Modus Partially Young.
Im Modus Young betrachtet der G1 nur Regionen der Kategorien Eden und Survivor. Anhand verschiedener Metriken wird entschieden welche dieser Regionen in das Collection Set aufgenommen werden. Ein Teil dieser Metiken sind Messungen der minimalen, maximalen und der durchschnittlichen Bereinigungszeit einer Region. Anhand dieser dynamischen (dynamisch, da sie sich im Verlauf anpassen) Kenngrößen wird die wahrscheinliche Ausführungszeit der Bereinigung einer Region berechnet (geschätzt). Sobald durch Hinzunahme einer Region die Summe der Ausführungszeiten aller Regionen im Collection Set einen Schwellwert überschreiten (konfigurierbar -XX:MaxGCPauseMillis=n), beginnt ein Garbage Collection Zyklus. Im Zuge dieser Garbage Collection werden alle gültigen Objekte in neue Regionen (Survivor oder Old) kopiert. Sobald alle gültigen Objekte eines Bereiches kopiert wurden, wird der gesamte Bereich freigegeben.
Sobald der Heap zu 45% gefüllt ist (konfigurierbar -XX:InitiatingHeapOccupancyPercent=n) schaltet der G1 in den Modus Partially Young. Das Bedeutet nichts geringeres als das nun auch Old Bereiche mit betrachtet werden. Da es jedoch dem Grundgedanken (Garbage First) des G1 widerspricht hier beliebige oder alle Bereiche zu wählen, werden die Bereiche wie folgt unterteilt:

  1. Bereiche ohne gültige Objekte, d.h. gesamter Bereich kann unmittelbar freigegeben werden
  2. Bereiche mit wenigen gültigen Objekten, d.h. nur wenige Objekte müssen kopiert werden
  3. Bereiche mit vielen gültigen Objekten, d.h. sehr viele Objekte müssen kopiert werden

Um dem Credo Garbage First gerecht zu werden, wird der G1 zuerst die Bereiche (1) und (2) in das Collection Set aufnehmen. Sobald keine oder nur noch wenige dieser Bereiche im Collection Set enthalten sind, fällt die Effizienz des G1. Wird dabei ein Schwellenwert unterschritten, so schaltet der G1 wieder in den Modus Young.

Sollte es dazu kommen das der Heap zu 80% oder mehr gefüllt ist, dann wird eine Full Garbage Collection durchgeführt. Das bedeutet, dass alle Regionen im Collection Set enthalten sind.

Konfiguration

Durch die JVM Option -XX:+UseG1GC kann der G1 als Garbage Collector aktiviert werden. Dabei ist zu prüfen ob die JVM startet und somit den G1 unterstützt (Java 6 ab Update 14 , Java 7). Weitere wichtige Optionen sind:

  • -XX:GCPauseIntervalMillis, Zeit zwischen dem Start zweier aufeinanderfolgender Garbage Collection Zyklen
  • -XX:MaxGCPauseMillis=n, maximale Länge der Garbage Collection Pause (dient nur als Schwellwert, um festzustellen wann das Collection Set genügend Regionen beinhaltet)
  • -XX:G1HeapRegionSize=n, Größe der Bereiche in MB
  • -XX:InitiatingHeapOccupancyPercent=n, prozentueller Anteil des Heaps der gefüllt sein muss damit eine Parially Young Garbage Collection startet

Wie beabsichtigt ist der G1 konfigurationsarm und vereinfacht daher die Handhabung.

Fazit

Nach langer Zeit in denen sich am grundlegenden Design der bestehenden Garbage Collectoren nichts geändert hat, ist nun eine neue vielversprechende Implementierung geliefert worden. Der meiner Meinung nach richtige Ansatz der Flexibilisierung und der Effizienzsteigerung muss weiter verfolgt werden um den G1 weiter zu verbessern oder ganz neue Implementierung der Speicherverwaltung zu entwickeln. Da der G1 erst in den neueren Java Versionen vorhanden ist, ist ein möglicher Einsatz in älteren Java Programmen, welche unter Umständen nicht kompatibel sind (beispielsweise Programme die nur Java 5 kompatibel sind), nicht möglich. Daher fehlen weitgehend Referenzdaten hinsichtlich seiner Leistungsfähigkeit (besonders im Vergleich zu derzeitigen Garbage Collectoren). Daher haben mein Kollege Ivan Senic und ich den G1 experimentell in dem NovaTec Tool inspectIT eingesetzt (ein Beitrag dazu folgt). Ein langfristiges Ziel ist es Daten verschiedenster Java Anwendungen zu sammeln, um so eine fundierte Vergleichsdatengrundlage zu schaffen. Jedoch bin ich eher skeptisch eine aussagekräftig Datengrundlagen zusammenstellen zu können (unter anderem da die Vergleichbarkeit von Anwendungen schwer möglich ist und da Projektverantwortliche interne Daten möglicherweise nicht öffentlich verfügbar machen können). Nichtsdestotrotz sollte die Hoffnung bleiben, dass neue Anwendungen auch den G1 mit in die Betrachtung ziehen werden bei der Auswahl oder Optimierung des eingesetzten Garbage Collectors.

 

–Thomas

Leave a Comment

By continuing to use the site, you agree to the use of cookies. more information

The cookie settings on this website are set to "allow cookies" to give you the best browsing experience possible. If you continue to use this website without changing your cookie settings or you click "Accept" below then you are consenting to this.

Close