1. Die Kernidee des Garbage-Collection-Algorithmus
Die Java-Sprache hat einen Garbage-Collection-Mechanismus eingerichtet, um verwendete Objekte zu verfolgen und nicht mehr verwendete (referenzierte) Objekte zu erkennen und zu recyceln. Dieser Mechanismus kann zwei Gefahren wirksam verhindern, die bei der dynamischen Speicherzuweisung auftreten können: Speichererschöpfung durch übermäßigen Speichermüll und illegale Speicherreferenzen durch unsachgemäße Speicherfreigabe.
Die Kernidee des Garbage-Collection-Algorithmus besteht darin, die Objekte im für die virtuelle Maschine verfügbaren Speicherplatz, also im Heap-Bereich, zu identifizieren. Wenn auf das Objekt verwiesen wird, wird es als lebend bezeichnet Objekt. Wenn das Objekt hingegen nicht mehr referenziert wird, wird es als lebendes Objekt bezeichnet, und der von ihm belegte Platz kann zur Neuzuweisung recycelt werden. Die Wahl des Garbage-Collection-Algorithmus und die angemessene Anpassung der Parameter des Garbage-Collection-Systems wirken sich direkt auf die Systemleistung aus, sodass Entwickler ein tieferes Verständnis benötigen.
2. Bedingungen für das Auslösen des Haupt-GC (Garbage Collector)
Die JVM führt sehr häufig eine sekundäre GC durch, aber da diese GC sehr wenig Zeit in Anspruch nimmt, hat sie nur geringe Auswirkungen auf das System. Was mehr Aufmerksamkeit verdient, ist die Auslösebedingung des Haupt-GC, da sie erhebliche Auswirkungen auf das System hat. Im Allgemeinen gibt es zwei Bedingungen, die den Haupt-GC auslösen:
(1) Wenn die Anwendung inaktiv ist, dh wenn kein Anwendungsthread ausgeführt wird, wird GC aufgerufen. Da GC im Thread mit der niedrigsten Priorität ausgeführt wird, wird der GC-Thread nicht aufgerufen, wenn die Anwendung ausgelastet ist, außer unter den folgenden Bedingungen.
(2) Wenn der Java-Heap-Speicher nicht ausreicht, wird GC aufgerufen. Wenn der Anwendungsthread ausgeführt wird und während des laufenden Prozesses neue Objekte erstellt und zu diesem Zeitpunkt nicht genügend Speicherplatz vorhanden ist, ruft die JVM den GC-Thread zwangsweise auf, um Speicher für neue Zuweisungen zurückzugewinnen. Wenn die Speicherzuweisungsanforderungen nach einem GC nicht erfüllt werden können, führt die JVM zwei weitere GC-Versuche für weitere Versuche durch. Wenn die Anforderungen immer noch nicht erfüllt werden können, meldet die JVM einen „Nicht genügend Speicher“-Fehler und die Java-Anwendung wird dies tun stoppen.
Da die JVM anhand der Systemumgebung entscheidet, ob der Haupt-GC durchgeführt werden soll und sich die Systemumgebung ständig ändert, ist der Betrieb des Haupt-GC ungewiss und es ist unmöglich vorherzusagen, wann dies zwangsläufig der Fall sein wird auftreten, aber sicher ist, dass bei einer Anwendung mit langer Laufzeit die Haupt-GC wiederholt durchgeführt wird.
3. Maßnahmen zur Reduzierung des GC-Overheads
Gemäß dem oben genannten GC-Mechanismus wirkt sich der Betrieb des Programms direkt auf Änderungen in der Systemumgebung aus und wirkt sich dadurch auf die Auslösung von GC aus. Wenn Sie nicht gemäß den Merkmalen von GC entwerfen und programmieren, treten eine Reihe negativer Auswirkungen auf, z. B. die Speichererhaltung. Um diese Effekte zu vermeiden, besteht das Grundprinzip darin, den Müll zu reduzieren und den Overhead im GC-Prozess so weit wie möglich zu reduzieren. Spezifische Maßnahmen umfassen die folgenden Aspekte:
(1) Rufen Sie System.gc() nicht explizit auf
Diese Funktion empfiehlt, dass die JVM die Haupt-GC durchführt. Dies ist jedoch nur ein Vorschlag und keine Garantie In vielen Fällen wird der Haupt-GC ausgelöst, wodurch die Häufigkeit des Haupt-GC erhöht wird, d. h. die Anzahl der intermittierenden Pausen erhöht wird. Hier muss besonders darauf geachtet werden, dass der im Code gezeigte Aufruf von System.gc () möglicherweise nicht unbedingt in der Lage ist, GC auszuführen. Wir können dies durch die Methode finalize () überprüfen, dh durch den aktiven Aufruf von System.gc (). Nicht unbedingt jedes Mal. Die finalize()-Methode wird jedes Mal aufgerufen. Das Merkmal der finalize()-Methode besteht darin, dass die finalize()-Methode zuerst aufgerufen wird, bevor das Objekt recycelt wird.
(2) Minimieren Sie die Verwendung temporärer Objekte
Temporäre Objekte werden zu Müll, nachdem sie aus Funktionsaufrufen herausgesprungen sind. Die Verwendung weniger temporärer Variablen ist gleichbedeutend mit einer Reduzierung der Müllerzeugung, wodurch das zweite oben erwähnte Problem verlängert wird . Der Zeitpunkt, zu dem eine Auslösebedingung auftritt, verringert die Wahrscheinlichkeit einer Haupt-GC.
(3) Es ist am besten, das Objekt explizit auf Null zu setzen, wenn es nicht verwendet wird
Im Allgemeinen werden Nullobjekte als Müll behandelt, daher ist es vorteilhaft, die nicht verwendeten Objekte explizit auf zu setzen Null. Der GC-Kollektor ermittelt Müll und verbessert dadurch die Effizienz von GC.
(4) Versuchen Sie, StringBuffer anstelle von String zu verwenden, um Strings zu akkumulieren (Einzelheiten finden Sie in einem anderen Blog-Artikel zu String und StringBuffer in JAVA).
Da String ein String-Objekt fester Länge ist, werden String-Objekte akkumuliert Anstatt ein String-Objekt zu erweitern, wird ein neues String-Objekt neu erstellt, z. B. Str5=Str1+Str2+Str3+Str4. Während der Ausführung dieser Anweisung werden mehrere Müllobjekte generiert, da das „+“ „Neu“ ist Während des Betriebs müssen String-Objekte erstellt werden, diese Übergangsobjekte haben jedoch keine praktische Bedeutung für das System und fügen nur noch mehr Müll hinzu. Um diese Situation zu vermeiden, können Sie StringBuffer zum Sammeln von Zeichenfolgen verwenden. Da StringBuffer eine variable Länge hat, wird es auf der ursprünglichen Basis erweitert und erzeugt keine Zwischenobjekte.
(5) Sie können Basistypen wie Int und Long anstelle von Integer- und Long-Objekten verwenden.
Basistypvariablen belegen viel weniger Speicherressourcen als entsprechende Objekte. Wenn dies nicht erforderlich ist, ist es am besten Verwenden Sie Basisvariablen. Wann müssen Sie Integer verwenden?
(6) Verwenden Sie so wenig statische Objektvariablen wie möglich
Statische Variablen sind globale Variablen und werden von GC nicht recycelt. Sie belegen immer Speicher.
(7) Verteilen Sie die Zeit zum Erstellen oder Löschen von Objekten.
Die Konzentration der Erstellung einer großen Anzahl neuer Objekte in einem kurzen Zeitraum, insbesondere großer Objekte, führt zu einem plötzlichen Bedarf an großen Objekten In dieser Situation kann nur die Haupt-GC der JVM durchgeführt werden, um Speicher zurückzugewinnen oder Speicherfragmente zu konsolidieren, wodurch die Häufigkeit der Haupt-GC erhöht wird. Das gleiche Prinzip gilt für die zentrale Löschung von Objekten. Dies führt dazu, dass plötzlich eine große Anzahl von Müllobjekten erscheint und der freie Speicherplatz zwangsläufig abnimmt, wodurch die Wahrscheinlichkeit, dass beim nächsten Erstellen eines neuen Objekts der Haupt-GC erzwungen wird, erheblich zunimmt.
4. Garbage-Collection-Algorithmus
(1) Referenzzählungssammler
Die Referenzzählung ist eine frühe Strategie für die Garbage-Collection. Bei diesem Ansatz verfügt jedes Objekt im Heap über einen Referenzzähler. Wenn ein Objekt erstellt und einer Variablen ein Verweis auf das Objekt zugewiesen wird, wird der Referenzzähler des Objekts auf 1 gesetzt. Erstellen Sie beispielsweise ein neues Objekt A a = new A (); und weisen Sie dann a einer anderen Variablen b zu, dh b = a. Wenn einer anderen Variablen ein Verweis auf dieses Objekt zugewiesen wird, wird der Zähler um eins erhöht. Wenn die Referenz eines Objekts die Lebensdauer überschreitet oder auf einen neuen Wert gesetzt wird, wird der Referenzzähler des Objekts um 1 dekrementiert. Wenn beispielsweise b=c ist, beträgt der Referenzzähler von a -1. Jedes Objekt mit einem Referenzzähler von 0 kann durch Garbage Collection erfasst werden. Wenn ein Objekt der Garbage Collection unterzogen wird, wird die Anzahl aller Objekte, auf die es verweist, um eins verringert. Bei diesem Ansatz kann die Garbage Collection eines Objekts zu nachfolgenden Garbage Collection-Aktionen für andere Objekte führen. Beispiel: A a=new A();b=a;
Vorteile der Methode: Der Referenzzählsammler kann schnell ausgeführt werden und ist mit dem Programmablauf verflochten. Diese Erinnerung ist in Echtzeitumgebungen von Vorteil, in denen das Programm nicht für längere Zeit unterbrochen werden kann. Nachteile der
-Methode: Die Referenzzählung kann keine Zyklen erkennen (d. h. zwei oder mehr Objekte verweisen aufeinander). Ein Beispiel für eine Schleife besteht darin, dass ein übergeordnetes Objekt einen Verweis auf ein untergeordnetes Objekt hat und das untergeordnete Objekt wiederum auf das übergeordnete Objekt verweist. Auf diese Weise ist es für Objektbenutzer unmöglich, einen Zählerstand von 0 zu haben, selbst wenn sie für das Root-Objekt des ausführenden Programms nicht mehr erreichbar sind. Ein weiterer Nachteil besteht darin, dass jede Erhöhung oder Verringerung des Referenzzählers zusätzlichen Overhead mit sich bringt.
(2) Tracing-Kollektor
Die Garbage-Erkennung wird normalerweise durch den Aufbau einer Sammlung von Root-Objekten und die Überprüfung der Erreichbarkeit ausgehend von diesen Root-Objekten implementiert. Ein Objekt ist erreichbar, wenn zwischen dem Root-Objekt, auf das das ausführende Programm zugreifen kann, und einem Objekt ein Referenzpfad besteht. Das Root-Objekt ist für das Programm immer zugänglich. Ausgehend von diesen Stammobjekten gilt jedes Objekt, das berührt werden kann, als „aktives“ Objekt. Objekte, die nicht berührt werden können, gelten als Müll, da sie die zukünftige Ausführung des Programms nicht mehr beeinflussen.
Der Tracing-Kollektor verfolgt den Objektreferenzgraphen ausgehend vom Wurzelknoten. Während der Verfolgung angetroffene Objekte werden auf eine bestimmte Weise markiert. Im Allgemeinen setzen Sie die Markierung entweder auf dem Objekt selbst oder verwenden eine separate Bitmap, um die Markierung zu setzen. Wenn die Verfolgung endet, sind nicht markierte Objekte nicht mehr erreichbar und können eingesammelt werden.
Der grundlegende Tracking-Algorithmus heißt „Mark and Clear“. Der Name weist auf die zwei Stufen von Junk-Telefonen hin. Während der Markierungsphase durchläuft der Garbage Collector den Referenzbaum und markiert jedes angetroffene Objekt. Während der Bereinigungsphase werden nicht markierte Objekte freigegeben und der nach der Freigabe der Objekte erhaltene Speicher wird an das ausführende Programm zurückgegeben. In der Java Virtual Machine muss der Bereinigungsschritt die Finalisierung des Objekts umfassen.
Ausführlichere und detailliertere Erklärungen zur Java-Garbage Collection und verwandte Artikel finden Sie auf der chinesischen PHP-Website!