Vor kurzem habe ich einen Job im Zusammenhang mit einem Java-Prozess, der viel Speicher benötigt, und ich musste GC kennen, damit ich zusammenfassen kann, was ich untersucht habe. Es ist eine Ära, in der ich nachschaue, aber ich habe es geschrieben, um meine Gedanken zu ordnen. Ich würde es begrüßen, wenn Sie darauf hinweisen könnten, dass die Möglichkeit besteht, dass Sie falsch verstehen.
Hinweis: In diesem Artikel geht es nicht um die neuesten GC-Angelegenheiten, sondern um die klassische Methode.
JVM Zunächst aus dem Grundmechanismus von Java. Java-Prozesse werden auf virtuellen Maschinen ausgeführt, die als JVMs bezeichnet werden. Dieser Mechanismus funktioniert auf verschiedenen Betriebssystemen und ermöglicht die Ausführung von kompiliertem Java-Code (Klassendateien) in verschiedenen Umgebungen, ohne sich um Unterschiede in der Umgebung sorgen zu müssen.
Es gibt verschiedene Arten von JVMs, in diesem Artikel wird jedoch die vom Open JDK verwendete HotSpot-VM vorausgesetzt. (Ich kenne den Unterschied zu anderen JVMs nicht)
Der Speicher wird zugewiesen, wenn Sie einen Java-Prozess starten. Der Bereich, der hauptsächlich verwendet wird, wird als Heap-Bereich bezeichnet, und Daten, die sich während der Ausführung der Verarbeitung dynamisch ändern, werden hier grundsätzlich zugeordnet. Es ist wichtig, die Speichernutzung in diesem Heap-Bereich für Anwendungen zu optimieren, die lange Zeit im selben Prozess ausgeführt werden.
Bei Angabe der Größe, die speziell als Option zugewiesen werden soll
-Xms20m -Xmx100m
Wenn Sie angeben Anfangsgröße des dem Heap-Bereich zugewiesenen Speichers: 20 MB Maximale Größe des dem Heap-Bereich zugewiesenen Speichers: 100 MB
Es wird die Einstellung sein.
Wenn Sie eine Anwendung erstellen, ohne den Status der Speichernutzung zu kennen, werden nicht mehr verwendete Garbage-Daten (Garbage) erstellt.
In diesem Artikel werde ich ein Beispiel für das Auftreten von Müll auf leicht verständliche Weise vorstellen. https://www.oracle.com/webfolder/technetwork/jp/javamagazine/Java-MA16-GC.pdf
Zum Beispiel, wenn Sie die folgende Klasse haben
class TreeNode {
public TreeNode left, right;
public int data;
TreeNode(TreeNode l, TreeNode r, int d) {
left = l; right = r; data = d;
}
public void setLeft(TreeNode l) { left = l;}
public void setRight(TreeNode r) {right = r;}
}
Erstellen Sie einen TreeNode wie folgt.
TreeNode left = new TreeNode(null, null, 13);
TreeNode right = new TreeNode(null, null, 19);
TreeNode root = new TreeNode(left, right, 17);
Dies bedeutet, dass der Wurzelknoten auf den linken und rechten Knoten verweist.
Angenommen, Sie haben einen Prozess hinzugefügt, um den richtigen Knoten zu ersetzen.
root.setRight(new TreeNode(null, null, 21));
Dann wird der 19. Knoten, der sich ursprünglich im richtigen Knoten befand, von niemandem referenziert und befindet sich in dem in der folgenden Abbildung gezeigten Zustand.
In diesem Zustand ist die TreeNode-Instanz mit data = 19 ein Objekt, auf das von niemandem verwiesen wird, sodass es zu einem Müll wurde.
Wenn weiterhin nicht verwendete Daten generiert werden, sammelt sich weiterhin nutzloser Speicher an und die Kapazität erreicht schließlich ihre Grenze. Um dies zu verhindern, ist GC (Garbage Collection) als Mechanismus erforderlich, um verschwendeten Speicher im Heap-Bereich automatisch freizugeben.
Wie bereits erwähnt, ist GC ein Mechanismus zum Freigeben von unnötigem Speicher. Es überprüft die Daten im Speicher, belässt sie als gültige Daten, wenn eine Referenz vorhanden ist, und gibt sie frei, wenn keine Referenz vorhanden ist, und urteilt, dass dies nicht erforderlich ist. Es ist jedoch ineffizient, den gesamten Speicherplatz einfach zu überprüfen, sodass er intern entsprechend der Existenzdauer der Daten verwaltet wird.
Junge Daten werden als junge Generation bezeichnet, alte Daten werden als alte Generation bezeichnet, und Daten, von denen bekannt ist, dass sie sich weniger wahrscheinlich ändern, werden als permanente Generation bezeichnet.
Grundsätzlich kommt es häufig zu einer Speicherzuweisung, aber die Idee, dass die meisten von ihnen nicht lange leben, wird in neue Daten (junge Generation) und langfristige Referenzdaten (alte Generation) unterteilt. Dies ermöglicht es, nur die in Young Generation enthaltenen Daten effizient zu überprüfen, indem auf den GC abgezielt wird.
Es gibt auch einen Bereich namens Permanent Generation, in dem Informationen zu geladenen Klassen gespeichert werden, die garantiert bis zu einem gewissen Grad gleich bleiben.
Referenz: JDK-Dokumentation öffnen
Es gibt mehrere GC-Algorithmen.
Und so weiter.
Dieses Mal werde ich die in Serial GC und Parallel GC verwendete Methode erläutern.
Der Young-Bereich des Heap-Bereichs ist in Eden und Survivor unterteilt, wie in der folgenden Abbildung gezeigt, und die GC wird durchgeführt, indem jeder Bereich gut genutzt wird.
Jeder Bereich hat die folgenden Rollen.
Eden Erster zugewiesener Speicherbereich
Survivor1, Survivor2 Daten, die nach der GC nicht freigegeben werden und nicht auf Alt gehen (der Einfachheit halber werden den beiden Typen nur 1 und 2 hinzugefügt).
Tenured Altes Ding. Daten, die nach einer bestimmten Häufigkeit von GC überleben, werden nach Alt migriert
Ein GC, der nur auf die junge Generation abzielt, wird als kleiner GC bezeichnet. Es hat die folgenden Funktionen.
Es ist schwer in Worten zu erklären, deshalb werde ich es mit einer Figur erklären.
Ein kleiner GC tritt auf, wenn neuer Speicher zugewiesen wird und Eden voll ist. Nicht referenzierte Daten werden gelöscht, aber gültige Daten werden in den Survivor-Bereich kopiert. Auch der Eden-Bereich wird komplett leer sein.
Wenn Eden in diesem Zustand wieder voll wird, tritt erneut eine geringfügige GC auf und das Ergebnis ist wie in der folgenden Abbildung gezeigt.
Diesmal habe ich Survivor 2 nach GC eingegeben. Im Bereich "Überlebende" werden die Daten entweder in den freien Speicherplatz kopiert und 1 und 2 werden hin und her verschoben. Ebenso wie Eden werden Daten gelöscht, auf die nicht aus dem Suvivor-Bereich verwiesen wird.
Als nächstes kommt die Beförderung zum Alten. Jedes Mal, wenn ein GC auftritt, werden die Daten von Young so oft aufgezeichnet, und wenn sie eine bestimmte Anzahl überschreiten, werden sie zu Alt verschoben.
Durch mehrmaliges Wiederholen der GC auf diese Weise erfolgt die Bewegung von Jung nach Alt. Diese Nummer kann optional angegeben werden, sodass Sie steuern können, wie oft Sie zu Alt wechseln.
-XX:MaxTenuringThreshold=N
FullGC Ich verstehe, wie Daten von Jung nach Alt übertragen werden, aber allein damit wird die Kapazität von Alt weiter zunehmen und die Kapazität wird irgendwo begrenzt sein. Hier kommt FullGC ins Spiel. FullGC tritt auf, wenn die Zuordnung zu Alt fehlschlägt und den Speicher einschließlich Alt und Jung bereinigt.
Dadurch wird Speicherplatz frei, der im Bereich "Alt" nicht mehr benötigt wird, und Sie können die Daten kopieren, die sich im Bereich "Überlebende" befanden.
Wie beim Minor GC wird die Anwendung während des Full GC gestoppt. Da die Stoppzeit lang ist, weil Alt enthalten ist, scheint es außerdem wichtig zu sein, das Auftreten zu unterdrücken, indem der Speicher im Bereich Jung so weit wie möglich verwendet wird.
Zusammenfassung
--Minor GC tritt auf, wenn der Eden-Bereich voll ist
Es stellte sich heraus, dass es ein Zyklus war.
Ich habe versucht, den grundlegenden Mechanismus von GC in Java zu organisieren. Ich war verwirrt über die Tatsache, dass es verschiedene Arten von JVMs gibt und was ich in jedem Artikel gesagt habe, etwas anders ist, aber ich beabsichtige, sie basierend auf den Quellen von Oracle und Open JDK zusammenzufassen. Nachdem der standardmäßig ausgewählte GC-Algorithmus G1GC ist, fasse ich ihn als Nächstes zusammen. Zuerst habe ich den zugrunde liegenden Mechanismus zur Speicherfreigabe eingeführt.
Open JDK Document Oracle Document JAVAMAGAZINE / Open JDK und neuer Garbage Collector JVM GC-Algorithmus und Tuning
Recommended Posts