Heim  >  Artikel  >  Java  >  Eine kurze Analyse des Java-Speichermodells und der Garbage Collection

Eine kurze Analyse des Java-Speichermodells und der Garbage Collection

高洛峰
高洛峰Original
2017-01-17 15:41:561260Durchsuche

1. Java-Speichermodell

Bei der Ausführung eines Programms teilt die Java Virtual Machine den von ihr verwalteten Speicher in mehrere Datenbereiche auf. Die Verteilung dieser Datenbereiche ist wie in der folgenden Abbildung dargestellt:

Eine kurze Analyse des Java-Speichermodells und der Garbage Collection

Programmzähler: ein kleiner Speicherbereich, der auf den aktuell ausgeführten Bytecode zeigt. Wenn der Thread eine Java-Methode ausführt, zeichnet dieser Zähler die Adresse der ausgeführten Bytecode-Anweisung der virtuellen Maschine auf. Wenn eine native Methode ausgeführt wird, ist dieser Zählerwert leer.

Java Virtual Machine Stack: Thread privat, sein Lebenszyklus stimmt mit dem Thread überein. Wenn jede Methode ausgeführt wird, wird ein Stapelrahmen erstellt, um Informationen wie lokale Variablentabellen, Operandenstapel, dynamische Links usw. zu speichern. Methodenexits usw. .

Lokaler Methodenstapel: Die Funktion ähnelt dem Stapel der virtuellen Maschine, mit der Ausnahme, dass der Stapel der virtuellen Maschine der virtuellen Maschine zur Ausführung von Java-Methoden dient, während der lokale Methodenstapel die verwendeten nativen Methoden bereitstellt.

Java-Heap: Dies ist der größte von der virtuellen Maschine verwaltete Speicherbereich, der von allen Threads gemeinsam genutzt wird. Dieser Bereich wird zum Speichern von Objektinstanzen verwendet. Der Java-Heap ist der Hauptbereich für das Speicherrecycling. Da die meisten aktuellen Sammler Generationssammlungsalgorithmen verwenden, kann der Java-Heap auch in die neue Generation und die alte Generation unterteilt werden. Es kann in den Eden-Raum, den Raum des Überlebenden, den Raum des Überlebenden usw. unterteilt werden. Gemäß der Java Virtual Machine-Spezifikation kann sich der Java-Heap in einem physisch diskontinuierlichen Raum befinden, solange er logisch kontinuierlich ist.

Methodenbereich: Wie Java wird es von jedem Thread gemeinsam genutzt und zum Speichern von Daten wie von der virtuellen Maschine geladenen Klasseninformationen, konstanten Lichtern, statischen Variablen und von Just-in-Time kompiliertem Code verwendet Compiler.

Laufzeitkonstantenpool ist Teil des Methodenbereichs. Zusätzlich zu den Beschreibungsinformationen wie Klassenversion, Feldern, Methoden, Schnittstellen usw. verfügt die Klassendatei auch über einen Konstantenpool , das zum Speichern verschiedener Literale und Symbolreferenzen verwendet wird, die während der Kompilierung generiert werden. Neue Konstanten können während des Betriebs in den Konstantenpool eingefügt werden. Die am häufigsten verwendete Methode ist die intern()-Methode der String-Klasse. Wenn eine String-Instanz intern nach derselben Unicode-String-Konstante sucht Wenn nicht, fügen Sie eine Unicode-Zeichenfolge hinzu, die der Instanzzeichenfolge im Konstantenpool entspricht, und geben Sie ihre Referenz zurück.

2. So ermitteln Sie Müllobjekte

Im Java-Heap sind mehrere Objektinstanzen gespeichert. Bevor der Garbage Collector den Heap recycelt, muss er zunächst feststellen, welche Objekte noch „lebendig“ sind. . Welche Objekte sind „tot“, das heißt, sie werden in keiner Weise verwendet.

Referenzzählmethode

Die Referenzzählmethode ist einfach zu implementieren, hocheffizient und in den meisten Fällen ein guter Algorithmus. Das Prinzip lautet: Immer wenn ein Verweis auf das Objekt vorhanden ist, erhöht sich der Zähler um 1. Wenn der Verweis abläuft, verringert sich der Zähler um 1. Wenn der Zählerwert 0 ist, bedeutet dies, dass das Objekt wird nicht mehr verwendet. Es ist zu beachten, dass die Referenzzählmethode das Problem der zirkulären Referenzen zwischen Objekten nur schwer lösen kann. Die gängige Java Virtual Machine verwendet die Referenzzählmethode nicht zur Speicherverwaltung.

Erreichbarkeitsanalyse-Algorithmus

Die Grundidee dieses Algorithmus besteht darin, eine Reihe von Objekten namens „GC Roots“ als Ausgangspunkt zu verwenden und von diesen Knoten aus nach unten zu suchen, um den Pfad zu suchen . Der übergebene Pfad wird als Referenzkette bezeichnet, die ein Objekt mit GC Roots verbindet (in der Graphentheorie bedeutet dies, dass das Objekt von GC Roots aus nicht erreichbar ist). Wie in der Abbildung gezeigt, sind die Objekte Objekt 5, Objekt 6 und Objekt 7 zwar miteinander verbunden, aber von GC Roots aus nicht erreichbar, sodass sie als wiederverwertbare Objekte beurteilt werden.

Eine kurze Analyse des Java-Speichermodells und der Garbage Collection

In der Java-Sprache können folgende Objekte als GC-Roots verwendet werden:

Im Stapel der virtuellen Maschine (lokale Variablentabelle im Stapelrahmen) das referenzierte Objekt.

Das Objekt, auf das die statische Eigenschaft der Klasse im Methodenbereich verweist.

Objekt, auf das Konstanten im Methodenbereich verweisen.

Objekt, auf das JNI (im Allgemeinen native Methode) im lokalen Methodenstapel verweist.

Nun stellt sich die Frage: Wird der Algorithmus zur Erreichbarkeitsanalyse Probleme mit Zirkelverweisen zwischen Objekten verursachen? Die Antwort lautet „Ja“, das heißt, es treten keine Zirkelverweisprobleme zwischen Objekten auf. GC Root ist ein speziell definierter „Startpunkt“ außerhalb des Objektdiagramms und kann von Objekten im Objektdiagramm nicht referenziert werden.

Sterben oder nicht sterben

Selbst unerreichbare Objekte im Erreichbarkeitsanalysealgorithmus sind nicht „notwendig, um zu sterben“. Zu diesem Zeitpunkt befinden sie sich vorübergehend in der „Probe“-Phase. Um ein Objekt wirklich für tot zu erklären, muss es mindestens zwei Markierungsprozesse durchlaufen. : Wenn nach der Erreichbarkeitsanalyse festgestellt wird, dass das Objekt keine mit GC Roots verbundene Referenzkette hat, wird es zum ersten Mal markiert und einmal gefiltert. Die Filterbedingung ist, ob dieses Objekt die finapze()-Methode ausführen muss . Wenn das Objekt die finapze()-Methode nicht abdeckt oder die finapze()-Methode von der virtuellen Maschine aufgerufen wurde, behandelt die virtuelle Maschine beide Situationen als „keine Ausführung erforderlich“. Das Programm kann einen „spannenden“ Selbstrettungsprozess durchführen, indem es finapze() überschreibt, aber dies hat nur eine Chance.

/** 
 * 此代码演示了两点: 
 * 1.对象可以在被GC时自我拯救。 
 * 2.这种自救的机会只有一次,因为一个对象的finapze()方法最多只会被系统自动调用一次 
 * @author zzm 
 */
pubpc class FinapzeEscapeGC { 
  
 pubpc static FinapzeEscapeGC SAVE_HOOK = null; 
  
 pubpc void isApve() { 
  System.out.println("yes, i am still apve :)"); 
 } 
  
 @Override
 protected void finapze() throws Throwable { 
  super.finapze(); 
  System.out.println("finapze mehtod executed!"); 
  FinapzeEscapeGC.SAVE_HOOK = this; 
 } 
  
 pubpc static void main(String[] args) throws Throwable { 
  SAVE_HOOK = new FinapzeEscapeGC(); 
  
  //对象第一次成功拯救自己 
  SAVE_HOOK = null; 
  System.gc(); 
  //因为finapze方法优先级很低,所以暂停0.5秒以等待它 
  Thread.sleep(500); 
  if (SAVE_HOOK != null) { 
SAVE_HOOK.isApve(); 
  } else { 
System.out.println("no, i am dead :("); 
  } 
  
  //下面这段代码与上面的完全相同,但是这次自救却失败了 
  SAVE_HOOK = null; 
  System.gc(); 
  //因为finapze方法优先级很低,所以暂停0.5秒以等待它 
  Thread.sleep(500); 
  if (SAVE_HOOK != null) { 
SAVE_HOOK.isApve(); 
  } else { 
System.out.println("no, i am dead :("); 
  } 
 } 
}

Das laufende Ergebnis ist:

finapze mehtod executed! 
yes, i am still apve :) 
no, i am dead :(

Dann reden wir über Referenzen

Ob es darum geht, die Anzahl der Referenzen auf ein Objekt mithilfe des Referenzzählalgorithmus zu beurteilen oder der Erreichbarkeitsanalysealgorithmus. Die Bestimmung, ob die Referenzkette des Objekts erreichbar ist und ob das Objekt aktiv ist, hängt alles mit „Referenzen“ zusammen. Vor JDK 1.2 war die Definition einer Referenz in Java sehr traditionell: Wenn der in den Referenztypdaten gespeicherte Wert die Startadresse eines anderen Speichers darstellt, wird gesagt, dass dieser Speicher eine Referenz darstellt. Nach JDK 1.2 erweiterte Java das Konzept der Referenzen und unterteilte Referenzen in vier Typen: Starke Referenz, Weiche Referenz, Schwache Referenz und Phantomreferenz. Diese vier Arten von Zitaten werden allmählich schwächer.

• Starke Referenzen beziehen sich auf Referenzen, die im Programmcode allgegenwärtig sind, wie zum Beispiel „Object obj = new Object()“. Solange die starken Referenzen noch vorhanden sind, wird der Garbage Collector sie niemals zurückfordern Objekt.

• Soft-Referenzen werden verwendet, um einige Objekte zu beschreiben, die nützlich, aber nicht notwendig sind. Bei Objekten, die Soft-Referenzen zugeordnet sind, werden diese Objekte beim zweiten Recycling in den Recyclingbereich aufgenommen, bevor im System eine Speicherüberlaufausnahme auftritt. Wenn für dieses Recycling nicht genügend Speicher vorhanden ist, wird eine Speicherüberlaufausnahme ausgelöst. Nach JDK 1.2 wird die SoftReference-Klasse zur Implementierung von Soft-Referenzen bereitgestellt.

• Schwache Referenzen werden auch zur Beschreibung nicht wesentlicher Objekte verwendet, ihre Stärke ist jedoch schwächer als weiche Referenzen. Objekte, die mit schwachen Referenzen verknüpft sind, können nur bis zur nächsten Speicherbereinigung überleben. Wenn der Garbage Collector funktioniert, werden Objekte, die nur schwachen Referenzen zugeordnet sind, recycelt, unabhängig davon, ob der aktuelle Speicher ausreicht. Nach JDK 1.2 wird die Klasse WeakReference bereitgestellt, um schwache Referenzen zu implementieren.

• Phantomreferenz wird auch Geisterreferenz oder Phantomreferenz genannt. Es handelt sich um die schwächste Art der Referenzbeziehung. Ob ein Objekt über eine virtuelle Referenz verfügt, hat keinen Einfluss auf seine Lebensdauer und es ist unmöglich, eine Objektinstanz über eine virtuelle Referenz abzurufen. Der einzige Zweck des Festlegens einer virtuellen Referenzzuordnung für ein Objekt besteht darin, eine Systembenachrichtigung zu erhalten, wenn das Objekt vom Collector zurückgefordert wird. Nach JDK 1.2 wird die PhantomReference-Klasse zur Implementierung virtueller Referenzen bereitgestellt.

Beispiel für die Verwendung weicher Referenzen:

package jvm;
 
import java.lang.ref.SoftReference;
 
class Node {
pubpc String msg = "";
}
 
pubpc class Hello {
pubpc static void main(String[] args) {
Node node1 = new Node(); // 强引用
node1.msg = "node1";
SoftReference<Node> node2 = new SoftReference<Node>(node1); // 软引用
node2.get().msg = "node2";
 
System.out.println(node1.msg);
System.out.println(node2.get().msg);
}
}

Das Ausgabeergebnis ist:

node2
node2

3. Typischer Garbage-Collection-Algorithmus

1. Mark-Sweep-Algorithmus (Mark-Clear)

Dies ist der grundlegendste Garbage-Collection-Algorithmus. Der Grund, warum er am einfachsten zu implementieren ist und die Idee am einfachsten ist . Der Mark-Sweep-Algorithmus ist in zwei Phasen unterteilt: die Mark-Phase und die Clear-Phase. Die Aufgabe der Markierungsphase besteht darin, alle Objekte zu markieren, die recycelt werden müssen, und die Räumphase besteht darin, den von den markierten Objekten eingenommenen Platz wiederherzustellen. Der spezifische Prozess ist in der folgenden Abbildung dargestellt:

Eine kurze Analyse des Java-Speichermodells und der Garbage Collection

Aus der Abbildung ist leicht ersichtlich, dass der Mark-Clear-Algorithmus relativ einfach zu implementieren ist, es jedoch einen schwerwiegenden Nachteil gibt Das Problem, dass es einfach ist, Speicherfragmente zu generieren, kann dazu führen, dass nicht genügend Speicherplatz gefunden wird, wenn im nachfolgenden Prozess Speicherplatz für große Objekte zugewiesen werden muss, was im Voraus eine neue Speicherbereinigungsaktion auslöst.

2. Kopieralgorithmus

Um die Mängel des Mark-Sweep-Algorithmus zu beheben, wurde der Kopieralgorithmus vorgeschlagen. Es teilt den verfügbaren Speicher je nach Kapazität in zwei gleich große Blöcke auf und nutzt jeweils nur einen davon. Wenn dieser Speicherblock erschöpft ist, kopieren Sie die verbleibenden Objekte in einen anderen Block und bereinigen Sie dann sofort den verwendeten Speicherplatz, damit das Problem der Speicherfragmentierung weniger wahrscheinlich auftritt. Der spezifische Prozess ist in der folgenden Abbildung dargestellt:

Eine kurze Analyse des Java-Speichermodells und der Garbage Collection

Obwohl dieser Algorithmus einfach zu implementieren, effizient im Betrieb und nicht anfällig für Speicherfragmentierung ist, nutzt er den Speicherplatz in hohem Maße aus Der Preis besteht darin, dass der nutzbare Speicher auf die Hälfte des Originals reduziert wird.

Offensichtlich hängt die Effizienz des Kopieralgorithmus eng mit der Anzahl der überlebenden Objekte zusammen. Wenn es viele überlebende Objekte gibt, wird die Effizienz des Kopieralgorithmus stark verringert.

3. Mark-Compact-Algorithmus (Mark-Compact)

Um die Mängel des Kopieralgorithmus zu beheben und den Speicherplatz voll auszunutzen, wird der Mark-Compact-Algorithmus vorgeschlagen. Die Markierungsphase dieses Algorithmus ist die gleiche wie bei Mark-Sweep, aber nach Abschluss der Markierung werden die wiederverwertbaren Objekte nicht direkt bereinigt, sondern die überlebenden Objekte an ein Ende verschoben und dann der Speicher außerhalb der Endgrenze bereinigt. Der spezifische Prozess ist in der folgenden Abbildung dargestellt:

Eine kurze Analyse des Java-Speichermodells und der Garbage Collection

4. Generationssammlungsalgorithmus (Generationssammlungsalgorithmus)

Der Generationssammlungsalgorithmus ist die Müllsammlung der meisten JVMs derzeit vom Prozessor verwendet. Die Kernidee besteht darin, den Speicher entsprechend dem Lebenszyklus des Objekts in mehrere unterschiedliche Bereiche zu unterteilen. Unter normalen Umständen ist der Heap-Bereich in die Tenured Generation und die Young Generation unterteilt. Das Merkmal der Old Generation besteht darin, dass bei jeder Müllsammlung nur eine kleine Anzahl von Objekten recycelt werden muss, während dies das Merkmal der Young Generation ist Bei jeder Müllsammlung muss eine große Anzahl von Objekten recycelt werden, sodass der am besten geeignete Sammlungsalgorithmus entsprechend den Merkmalen verschiedener Generationen übernommen werden kann.

Derzeit übernehmen die meisten Garbage Collectors den Kopieralgorithmus für die neue Generation, da die meisten Objekte für jede Garbage Collection in der neuen Generation wiederhergestellt werden müssen, was bedeutet, dass die Anzahl der Kopiervorgänge geringer ist, in der Praxis jedoch nicht Der Raum der neuen Generation wird nicht im Verhältnis 1:1 aufgeteilt. Im Allgemeinen wird die neue Generation in einen größeren Eden-Raum und zwei kleinere Survivor-Räume aufgeteilt (normalerweise 8:1:1). Kopieren Sie beim Recycling die überlebenden Objekte im Eden- und Survivor-Bereich in einen anderen Survivor-Bereich und räumen Sie dann Eden und den gerade verwendeten Survivor-Bereich auf.

Da das Merkmal der alten Generation darin besteht, dass jedes Mal nur eine kleine Anzahl von Objekten recycelt wird, wird im Allgemeinen der Mark-Compact-Algorithmus verwendet.

Die obige kurze Analyse des Java-Speichermodells und der Garbage Collection ist der gesamte vom Herausgeber geteilte Inhalt. Ich hoffe, dass er Ihnen eine Referenz geben kann, und ich hoffe, dass Sie die chinesische PHP-Website unterstützen.

Weitere Artikel zum Java-Speichermodell und zur Garbage Collection finden Sie auf der chinesischen PHP-Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn