Java Virtual Machine - Generations-GCs
Die meisten JVMs teilen den Haufen in drei Generationen - the young generation (YG), the old generation (OG) and permanent generation (also called tenured generation). Was sind die Gründe für ein solches Denken?
Empirische Studien haben gezeigt, dass die meisten Objekte, die erstellt werden, eine sehr kurze Lebensdauer haben -
Quelle
https://www.oracle.com
Wie Sie sehen können, wird die Anzahl der überlebenden Bytes (im Allgemeinen) geringer, da immer mehr Objekte mit der Zeit zugewiesen werden. Java-Objekte weisen eine hohe Sterblichkeitsrate auf.
Wir werden uns ein einfaches Beispiel ansehen. Die String-Klasse in Java ist unveränderlich. Dies bedeutet, dass Sie jedes Mal, wenn Sie den Inhalt eines String-Objekts ändern müssen, ein neues Objekt erstellen müssen. Nehmen wir an, Sie nehmen 1000-mal Änderungen an der Zeichenfolge in einer Schleife vor, wie im folgenden Code gezeigt -
String str = “G11 GC”;
for(int i = 0 ; i < 1000; i++) {
str = str + String.valueOf(i);
}
In jeder Schleife erstellen wir ein neues Zeichenfolgenobjekt, und die während der vorherigen Iteration erstellte Zeichenfolge wird unbrauchbar (dh sie wird von keiner Referenz referenziert). Die Lebensdauer dieses Objekts war nur eine Iteration - sie werden vom GC in kürzester Zeit erfasst. Solche kurzlebigen Gegenstände werden im Bereich der jungen Generation des Haufens aufbewahrt. Das Sammeln von Objekten der jungen Generation wird als kleine Müllabfuhr bezeichnet und verursacht immer eine Pause in der Welt.
Wenn die junge Generation voll ist, führt der GC eine kleine Müllabfuhr durch. Tote Objekte werden verworfen und lebende Objekte werden in die alte Generation verschoben. Die Anwendungsthreads werden während dieses Vorgangs angehalten.
Hier sehen wir die Vorteile, die ein solches Generationsdesign bietet. Die junge Generation ist nur ein kleiner Teil des Haufens und wird schnell voll. Die Verarbeitung dauert jedoch viel kürzer als die Verarbeitung des gesamten Heaps. Die 'Stop-the-World'-Pausen sind in diesem Fall also viel kürzer, wenn auch häufiger. Wir sollten immer kürzere Pausen als längere anstreben, auch wenn diese häufiger auftreten. Wir werden dies in späteren Abschnitten dieses Tutorials ausführlich besprechen.
Die junge Generation ist in zwei Räume unterteilt - eden and survivor space. Objekte, die während der Sammlung von Eden überlebt haben, werden in den Überlebensraum verschoben, und diejenigen, die den Überlebensraum überleben, werden in die alte Generation verschoben. Die junge Generation wird verdichtet, während sie gesammelt wird.
Wenn Objekte in die alte Generation verschoben werden, füllt sie sich schließlich und muss gesammelt und verdichtet werden. Unterschiedliche Algorithmen verfolgen hierzu unterschiedliche Ansätze. Einige von ihnen stoppen die Anwendungsthreads (was zu einer langen "Stop-the-World" -Pause führt, da die alte Generation im Vergleich zur jungen Generation ziemlich groß ist), während einige dies gleichzeitig tun, während die Anwendungsthreads weiterlaufen. Dieser Vorgang wird als vollständige GC bezeichnet. Zwei solche Sammler sindCMS and G1.
Lassen Sie uns diese Algorithmen nun im Detail analysieren.
Serielle GC
Dies ist der Standard-GC auf Computern der Clientklasse (Einzelprozessorcomputer oder 32b JVM, Windows). In der Regel sind GCs stark multithreaded, der serielle GC jedoch nicht. Es verfügt über einen einzelnen Thread zum Verarbeiten des Heapspeichers und stoppt die Anwendungsthreads, wenn ein kleinerer oder ein größerer GC ausgeführt wird. Wir können der JVM befehlen, diesen GC zu verwenden, indem wir das Flag angeben:-XX:+UseSerialGC. Wenn ein anderer Algorithmus verwendet werden soll, geben Sie den Algorithmusnamen an. Beachten Sie, dass die alte Generation während eines großen GC vollständig verdichtet wird.
Durchsatz GC
Dieser GC ist standardmäßig auf 64b-JVMs und Multi-CPU-Computern verfügbar. Im Gegensatz zum seriellen GC werden mehrere Threads verwendet, um die junge und die alte Generation zu verarbeiten. Aus diesem Grund wird der GC auch als GC bezeichnetparallel collector. Wir können unserer JVM befehlen, diesen Kollektor mithilfe des Flags zu verwenden:-XX:+UseParallelOldGC oder -XX:+UseParallelGC(ab JDK 8). Die Anwendungsthreads werden gestoppt, während eine größere oder eine kleinere Speicherbereinigung durchgeführt wird. Wie der Serienkollektor verdichtet er die junge Generation während eines großen GC vollständig.
Der Durchsatz-GC sammelt das YG und das OG. Wenn der Eden voll ist, wirft der Sammler lebende Objekte aus ihm entweder in das OG oder in einen der Überlebensräume (SS0 und SS1 im folgenden Diagramm). Die toten Objekte werden weggeworfen, um den von ihnen belegten Platz freizugeben.
Vor der GC von YG
Nach GC von YG
Während eines vollständigen GC leert der Durchsatzkollektor das gesamte YG, SS0 und SS1. Nach der Operation enthält das OG nur noch lebende Objekte. Wir sollten beachten, dass beide oben genannten Kollektoren die Anwendungsthreads während der Verarbeitung des Heaps stoppen. Dies bedeutet lange „Stopthe-World“ -Pausen während eines großen GC. Die nächsten beiden Algorithmen zielen darauf ab, sie auf Kosten von mehr Hardwareressourcen zu eliminieren -
CMS Collector
Es steht für "Concurrent Mark-Sweep". Seine Funktion besteht darin, dass einige Hintergrund-Threads verwendet werden, um die alte Generation regelmäßig zu durchsuchen und tote Objekte zu entfernen. Während eines kleinen GC werden die Anwendungsthreads jedoch gestoppt. Die Pausen sind jedoch recht klein. Dies macht das CMS zu einem Low-Pause-Kollektor.
Dieser Kollektor benötigt zusätzliche CPU-Zeit, um den Heap zu durchsuchen, während die Anwendungsthreads ausgeführt werden. Außerdem sammeln die Hintergrund-Threads nur den Heap und führen keine Komprimierung durch. Sie können dazu führen, dass der Haufen fragmentiert wird. Da dies so weitergeht, stoppt das CMS nach einem bestimmten Zeitpunkt alle Anwendungsthreads und komprimiert den Heap mit einem einzigen Thread. Verwenden Sie die folgenden JVM-Argumente, um die JVM anzuweisen, den CMS-Kollektor zu verwenden:
“XX:+UseConcMarkSweepGC -XX:+UseParNewGC” als JVM-Argumente, um es anzuweisen, den CMS-Kollektor zu verwenden.
Vor dem GC
Nach der GC
Beachten Sie, dass die Erfassung gleichzeitig durchgeführt wird.
G1 GC
Dieser Algorithmus unterteilt den Heap in mehrere Regionen. Wie der CMS-Kollektor stoppt er die Anwendungsthreads, während er einen kleinen GC ausführt, und verwendet Hintergrundthreads, um die alte Generation zu verarbeiten, während die Anwendungsthreads am Laufen bleiben. Da die alte Generation in Regionen unterteilt wurde, werden sie immer wieder komprimiert, während Objekte von einer Region in eine andere verschoben werden. Daher ist die Fragmentierung minimal. Sie können die Flagge verwenden:XX:+UseG1GCum Ihre JVM anzuweisen, diesen Algorithmus zu verwenden. Wie CMS benötigt es auch mehr CPU-Zeit, um den Heap zu verarbeiten und die Anwendungsthreads gleichzeitig auszuführen.
Dieser Algorithmus wurde entwickelt, um größere Haufen (> 4G) zu verarbeiten, die in verschiedene Regionen unterteilt sind. Einige dieser Regionen umfassen die junge Generation, der Rest die alte. Das YG wird traditionell gelöscht - alle Anwendungsthreads werden gestoppt und alle Objekte, die für die alte Generation oder den Überlebensbereich noch am Leben sind.
Beachten Sie, dass alle GC-Algorithmen den Heap in YG und OG unterteilt haben und ein STWP verwenden, um das YG zu löschen. Dieser Vorgang ist normalerweise sehr schnell.