|
|||||||||||||||||||||||||||||||||||
HOME | COURSES | TALKS | ARTICLES | GENERICS | LAMBDAS | IOSTREAMS | ABOUT | CONTACT | | | | |||||||||||||||||||||||||||||||||||
|
Der Garbage-First Garbage Collector (G1) - Übersicht über die Funktionalität
|
||||||||||||||||||||||||||||||||||
In den letzten Beiträgen (siehe /
GC5
/ und
/
GC6
/)haben wir uns die Architektur und das Tuning der
bisherigen Generational Garbage Collection der Sun JVM angesehen.
Seit einigen Jahren arbeitet Sun schon an einem ganz neuen Garbage Collector
mit einer neuen, eigenständigen Architektur. Garbage-First Garbage
Collector (oder kurz: G1) heißt er. Wir wollen uns ihn in diesem
Artikel genauer ansehen.
MotivationMit dem CMS (siehe / GC4 /) gibt es auf der Old Generation einen Garbage Collector, dessen Charakteristikum möglichst kurze Pausen (d.h. möglichst kurze Unterbrechungen der Anwendung durch den Garbage Collector) sind. Leider ist die Benutzung des CMS unter Umständen ziemlich tricky’. Kann er seine Arbeit nicht schnell genug erledigen oder kommt es zu so starker Heap-Fragmentierung, dass neue Objekte nicht mehr in der Old Generation angelegt werden können, wird eine Full Collection ausgelöst, die die Anwendung ziemlich lange unterbricht. Hier kann man dann zwar mit explizitem Tuning versuchen, die Full Collections zu vermeiden (siehe / GC4 /); es wird aber nicht immer gelingen. Ein weiteres Problem ist, dass bei Veränderung des Speicherverhaltens (zum Beispiel nach Einbau neuer Features) ein Tuning wieder nötig wird.An dieser Stelle soll der G1 nun Abhilfe schaffen. Er soll konstant kurze Pausen, bei gutem bis akzeptablem Durchsatz, liefern. Dazu kommt, dass er einfach an die spezifischen Anforderungen angepasst werden kann. Man kann von außen mitgeben, Dies geht im Detail so, dass man eine Untergrenze für den Abstand zwischen dem Start zweier aufeinander folgender Garbage Collection Pausen und eine Obergrenze für die Pause selbst vorgibt. Dies geht mit Hilfe von JVM Optionen. Die Option für die Untergrenze des Abstands zwischen den Pausenstarts ist –XX:GCPauseIntervalMillis (Defaultwert: 500ms) und die Option für die Obergrenze der Pause ist –XX:MaxGCPauseMillis (Defaultwert: 200ms). Klarerweise muss GCPauseIntervalMillis größer als MaxGCPauseMillis sein, damit zwischen dem Start der Garbage Collections Pausen auch noch Zeit für die Anwendung bleibt und nicht nur für die Garbage Collection Pause. Wenn man die Ungleichheitsrelation bei den Vorgaben nicht einhält, bricht die JVM den Start ab mit einer Fehlermeldung, die auf diesen Umstand hinweist.
Abbildung 1 zeigt beispielhaft die Verteilung der CPU auf Applikation und Garbage Collector zusammen mit den definierenden Vorgaben. Wie der Garbage Collector dafür sorgt die Vorgaben einzuhalten, werden wir uns im nächsten Artikel im Detail ansehen. Fangen wir hier erst mal mit einer Übersicht zum Aufbau und zur Funktionalität des G1 an. Funktionale ÜbersichtDie Motivation für die Entwicklung des G1 geht zwar über den Vergleich mit dem CMS. Trotzdem ist der G1 anders als der CMS nicht nur ein Old Generation Garbage Collector. Er ist vielmehr ein völlig neuer Garbage Collector für den gesamten User-Heap mit einer neuen eigenständigen Architektur. Der Perm-Bereich (siehe / GC1 /) arbeitet aber wie bisher. Deshalb gehen wir auf ihn in diesem Artikel nicht weiter ein.Aufteilung in RegionDer gesamte User-Heap wird beim G1 in lauter gleichgroße Regionen ( regions ) aufgeteilt. In den ersten Versionen des G1 waren die Regionen ein MByte groß. Seit dem Release 1.6.0_18 können die Regionsgröße nun 1 bis 32 MByte sein (zusätzlich muss es auch eine Potenz von 2 sein). Der G1 wählt die Größe nach der einfachen Regel: kleiner Heap - kleine Regionsgröße, großer Heap - große Regionsgröße. Sollte der G1 den Heap während des Programmablaufs vergrößern, wird die Regionsgröße aber nicht mehr verändert. Die Regionsgröße lässt sich auch von außen festlegen. Mit der JVM-Option -XX:G1HeapRegionSize=8M wird sie zum Beispiel auf acht MByte gesetzt.Der G1 verwendet eine spezifische Art von Generational Garbage Collection . Entsprechend enthält eine Region nur junge Objekte ( Young Region ) oder nur alte Objekte (Old Region). Natürlich kann eine Region auch aktuell gar nicht genutzt werden und leer sein. Die Young Regions teilen sich ihrerseits noch mal in zwei Typen von Regionen auf. Da sind zum einen die Regionen, in denen aktuell Objekte angelegt werden, und zum anderen die Survivor Regions. Die Survivor Regions haben eine ähnliche Aufgabe wie die Survivor Spaces (siehe / GC1 /) bei der bisherigen Garbage Collection: Sie nehmen Objekte auf, die bei der Garbage Collection (beim G1 spricht man meist von Evacuation ) aus einer Young Region herauskopiert wurden, aber noch nicht direkt in eine Old Region kopiert werden sollten.
Abbildung 2 zeigt einen beispielhaften G1 User-Heap. Man sieht die Aufteilung in gleichgroße Regionen. Auffallend im Vergleich zur bisherigen Garbage Garbage Collection ist, dass die logisch zusammengehörenden Regionen (hier: die drei Regionen der Old Generation) keinen zusammenhängenden Speicher belegen müssen. Die Zuordnung von Region zu enthaltenem Typ von Objekten ist im G1 immer nur temporär. So kann eine Old Region nach ihrer Evakuierung für die Allokation neuer Objekte, als Survivor Region, als Old Region oder auch erst mal gar nicht genutzt werden. Das hängt allein davon ab, welcher Typ von Region benötigt wird. Objekt AllokationWie bereits beschrieben gibt es eine oder mehrere Regionen, die speziell dazu benutzt werden, dass in ihnen neuen Objekte angelegt werden. Wie auch schon bei den bisherigen Garbage Collectoren (siehe / GC2 /) erfolgt die Allokation mit Hilfe von TLABs ( Thread Allocation Buffer ). Das heißt, jedem Anwendungsthread wird ein spezifischer Bereich in der Region (der TLAB) zugeordnet, in dem er ohne weitere Synchronisation neue Objekte anlegen kann. Die Zuordnung des TLABs zu dem Anwendungsthreads erfolgt mit einer Compare-And-Swap-Operation (CAS). CAS-Operationen sind atomar und können deshalb ohne Synchronisation ausgeführt werden (siehe /CAS/). Daraus ergibt sich ein sehr geringer Synchronisationsoverheads zwischen den Anwendungsthreads, so dass sichergestellt ist, dass die Objekt Allokation auch auf einer Multicore-/Multiprozessor-Plattform gut skaliert.Für größere Objekte gibt es spezielle Allokationsstrategien. Je nach Größe werden sie in eigenen TLABs oder sogar in eigenen Regionen angelegt. Objekt EvakuierungDie überlebenden Objekte einer Young Region werden bei der Garbage Collection je nach Alter und Anzahl entweder in eine Survivor Region oder eine Old Region kopiert. Der Speicher der Young Region wird danach zur Wiederverwendung freigegeben. Es wird also ähnlich wie bei der Young Generation der bisherigen Garbage Collection ein Scavenger Ansatz verwendet (siehe / GC2 /).Die Evakuierung der lebenden Objekte erfolgt in einer Garbage Collection Pause; das heißt, die Anwendungsthreads stehen so lange. Die Evakuierung ist die letzte Tätigkeit in der Pause. Vorher werden verschiedene Verwaltungsarbeiten durchgeführt, die dazu dienen, die überlebenden Objekte zu bestimmen. Was dabei genau geschieht, schauen wir uns im Detail im nächsten Artikel an. Wichtig ist, dass alle Tätigkeiten der Garbage Collection Pause in relativ kleine Teile ( Sub Tasks ) zerlegt werden, die parallel zueinander durch verschiedene Garbage Collector Threads bearbeitet werden können. So wird erreicht, dass die Garbage Collection gut auf einer Multicore-/Multiprozessor-Plattform skaliert und die Garbage Collection Pause möglichst klein ist. Defaultmäßig werden soviel Garbage Collector Threads genutzt, wie es Prozessorcores auf der Plattform gibt. Old Regions werden bei der Garbage Collection nur dann berücksichtigt, wenn sie nur noch relativ wenig lebende Objekte enthalten; oder anders formuliert: wenn sie viel Müll (Garbage) enthalten. Daher kommt auch der Name Garbage-First (kurz: G1) Garbage Collector. Old Regions kann man bezüglich der Garbage Collection in drei Kategorien aufteilen:
Ob überhaupt Old Regions bei der Garbage Collection berücksichtigt
werden, hängt von dem Modus ab, in dem der G1 gerade läuft. Es
gibt zwei Modi:
Fully Young Mode
und
Partially Young Mode
.
Im Fully Young Mode gehören alle Young Regions zum Collections Set.
Beim Partially Young Mode besteht der Collection Set auch wieder aus allen
Young Regions; zusätzlich kommen die Old Regions dazu, die keine oder
relativ wenig lebende Objekte enthalten. Der G1 schaltet während
des Ablaufs dynamisch zwischen den Modi hin und her. Welche Kriterien
er dafür verwendet, schauen wir uns im nächsten Artikel genauer
an.
Abbildung 3 zeigt ein Beispiel für eine G1 Garbage Collection im Partially Young Mode, d.h. Young und Old Regions werden berücksichtigt. Die linke Seite zeigt den Heap vor der Collection, die rechte Seite danach. Die kleinen weißen Punkte in den Regionen symbolisieren die lebenden Objekte.
Die Old Region oben links enthält noch zu viele lebende Objekte.
Sie gehört deshalb nicht zum Collection Set und wird daher nicht in
die Garbage Collection mit einbezogen. Die Old Region ganz rechts
enthält gar keine lebenden Objekte und wird direkt freigegeben.
Die lebenden Objekte aus den anderen drei Regionen - es sind zwei Young
Regions (bestehend aus je einer Allocation Region und einer Survivor Region)
und eine Old Region - werden in eine neue Old Region kopiert.
Danach werden diese Regionen freigegeben.
G1 und JDK VersionenAls der G1 mit JDK 6 Update 14 zum ersten Mal in einer offiziellen Java Version verfügbar war, gab es Missverständnisse bezüglich der Release Notes. Die Orignalformulierung konnte so verstanden werden, dass man den G1 nicht nutzen dürfe, wenn man keinen Supportvertrag mit Sun hatte. Die überarbeitete Formulierung machte dann klar, dass ohne Supportvertrag die Benutzung auf eigene Gefahr möglich ist. Entsprechen muss man, wenn man G1 mit JDK 6 nutzen will, nicht nur die JVM Option –XX:+UseG1GC setzen, die den G1 als Garbage Collector auswählt, sondern zusätzlich auch –XX:+UnlockExperimentalVMOptions .Der fertige und vollständig unterstütze G1 war für den JDK 7 offiziell angekündigt. Entsprechend brauchte man bei neueren Vorabversionen des JDK 7 aus dem OpenJDK die Option –XX:+UnlockExperimentalVMOptions nicht mehr. Bis zu welcher Produktionsversionsnummer die Experimental-Option genau benötigt wird, hängt von der Betriebssystem-Plattform (Windows oder Linux) ab. Als klar wurde, dass der Releasetermin für den JDK 7 mit September 2010 nicht eingehalten werden konnte, wurden die schon fast fertigen Teile der bisherigen Entwicklung als neuer JDK 7 auf Mitte 2011 und die anderen Teile als JDK 8 auf Ende 2012 verschoben. Für den G1 hatte man eine besonders salomonische Entscheidung parat: er ist offiziell Teil des JDK 6. DanksagungAbgesehen von einem JavaOne Vortrag aus dem Jahre 2008 (siehe /JONE/) und einem White Paper für das International Symposium on Memory Management 2004 (siehe /ISMM/) gibt es bis heute kaum substantielle Veröffentlichungen zu G1. Dieser Artikel basiert daher neben den oben erwähnten Quellen zum größten Teil auf Informationen, die wir direkt von Tony Printezis (dem Chefentwickler von G1) erhalten haben. Dafür möchten wir uns an dieser Stelle noch einmal recht herzlich bei ihm bedanken.ZusammenfassungUm verlässliche kurze Pausen bei der Garbage Collection zu haben, wurde von Sun/Oracle der G1 Garbage Collector entwickelt. Dieser Artikel war eine Übersicht über die Funktionalität des G1. Im nächsten Artikel schauen wir uns dann die Details an.Literaturverweise
Die gesamte Serie über Garbage Collection:
|
|||||||||||||||||||||||||||||||||||
© Copyright 1995-2012 by Angelika Langer. All Rights Reserved. URL: < http://www.AngelikaLanger.com/Articles/EffectiveJava/55.GC.G1.Overview/55.GC.G1.Overview.html> last update: 1 Mar 2012 |