Speicher in der JVM verstehen
Wie Sie bereits wissen, führt die JVM Java-Programme in sich selbst aus. Wie jede virtuelle Maschine verfügt sie über ein eigenes Speicherorganisationssystem.
Das interne Speicherlayout zeigt an, wie Ihre Java-Anwendung funktioniert. Auf diese Weise können Engpässe im Betrieb von Anwendungen und Algorithmen identifiziert werden. Mal sehen, wie es funktioniert.
Wichtig! Das ursprüngliche Java-Modell war nicht gut genug und wurde daher in Java 1.5 überarbeitet. Diese Version wird bis heute verwendet (Java 14+).
Thread-Stapel
Das intern von der JVM verwendete Java-Speichermodell unterteilt den Speicher in Thread-Stacks und Heaps. Schauen wir uns das Java-Speichermodell an, logisch in Blöcke unterteilt:
Alle in der JVM ausgeführten Threads verfügen über einen eigenen Stapel . Der Stack wiederum enthält Informationen darüber, welche Methoden der Thread aufgerufen hat. Ich werde dies den „Aufrufstapel“ nennen. Der Aufrufstapel wird fortgesetzt, sobald der Thread seinen Code ausführt.
Der Stapel des Threads enthält alle lokalen Variablen, die zum Ausführen von Methoden auf dem Stapel des Threads erforderlich sind. Ein Thread kann nur auf seinen eigenen Stapel zugreifen. Lokale Variablen sind für andere Threads nicht sichtbar, sondern nur für den Thread, der sie erstellt hat. In einer Situation, in der zwei Threads denselben Code ausführen, erstellen beide ihre eigenen lokalen Variablen. Somit hat jeder Thread seine eigene Version jeder lokalen Variablen.
Alle lokalen Variablen primitiver Typen ( boolean , byte , short , char , int , long , float , double ) werden vollständig im Thread-Stapel gespeichert und sind für andere Threads nicht sichtbar. Ein Thread kann eine Kopie einer primitiven Variablen an einen anderen Thread übergeben, aber keine primitive lokale Variable gemeinsam nutzen.
Haufen
Der Heap enthält alle in Ihrer Anwendung erstellten Objekte, unabhängig davon, welcher Thread das Objekt erstellt hat. Dazu gehören Wrapper primitiver Typen (z. B. Byte , Integer , Long usw.). Es spielt keine Rolle, ob das Objekt erstellt und einer lokalen Variablen zugewiesen oder als Mitgliedsvariable eines anderen Objekts erstellt wurde, es wird auf dem Heap gespeichert.
Unten ist ein Diagramm, das den Aufrufstapel und lokale Variablen (sie werden auf Stapeln gespeichert) sowie Objekte (sie werden auf dem Heap gespeichert) veranschaulicht:
Wenn die lokale Variable einen primitiven Typ hat, wird sie auf dem Stapel des Threads gespeichert.
Eine lokale Variable kann auch eine Referenz auf ein Objekt sein. In diesem Fall wird die Referenz (lokale Variable) auf dem Thread-Stack gespeichert, das Objekt selbst jedoch auf dem Heap.
Ein Objekt enthält Methoden, diese Methoden enthalten lokale Variablen. Diese lokalen Variablen werden auch auf dem Thread-Stack gespeichert, selbst wenn das Objekt, dem die Methode gehört, auf dem Heap gespeichert ist.
Die Mitgliedsvariablen eines Objekts werden zusammen mit dem Objekt selbst auf dem Heap gespeichert. Dies gilt sowohl, wenn die Mitgliedsvariable einen primitiven Typ hat, als auch wenn es sich um eine Objektreferenz handelt.
Zusammen mit der Klassendefinition werden auch statische Klassenvariablen auf dem Heap gespeichert.
Interaktion mit Objekten
Auf Objekte auf dem Heap kann von allen Threads zugegriffen werden, die einen Verweis auf das Objekt haben. Wenn ein Thread Zugriff auf ein Objekt hat, kann er auf die Variablen des Objekts zugreifen. Wenn zwei Threads gleichzeitig eine Methode für dasselbe Objekt aufrufen, haben beide Zugriff auf die Mitgliedsvariablen des Objekts, aber jeder Thread verfügt über seine eigene Kopie der lokalen Variablen.
Zwei Threads verfügen über eine Reihe lokaler Variablen.Lokale Variable 2zeigt auf ein gemeinsames Objekt auf dem Heap (Objekt 3). Jeder der Threads verfügt über eine eigene Kopie der lokalen Variablen mit eigener Referenz. Ihre Referenzen sind lokale Variablen und werden daher auf Thread-Stacks gespeichert. Allerdings verweisen zwei unterschiedliche Referenzen auf dasselbe Objekt auf dem Heap.
Bitte beachten Sie, dass die allgemeineObjekt 3hat Links zuObjekt 2UndObjekt 4als Mitgliedsvariablen (dargestellt durch Pfeile). Über diese Links können zwei Threads darauf zugreifenObjekt 2UndObjekt4.
Das Diagramm zeigt auch eine lokale Variable (lokale Variable 1aus methodTwo ). Jede Kopie davon enthält unterschiedliche Referenzen, die auf zwei verschiedene Objekte verweisen (Objekt 1UndObjekt 5) und nicht dasselbe. Theoretisch können beide Threads auf beide zugreifenObjekt 1, alsoObjekt 5wenn sie Verweise auf diese beiden Objekte haben. Aber im Diagramm oben hat jeder Thread nur einen Verweis auf eines der beiden Objekte.
Ein Beispiel für die Interaktion mit Objekten
Sehen wir uns an, wie wir die Arbeit im Code demonstrieren können:
public class MySomeRunnable implements Runnable() {
public void run() {
one();
}
public void one() {
int localOne = 1;
Shared localTwo = Shared.instance;
//... do something with local variables
two();
}
public void two() {
Integer localOne = 2;
//... do something with local variables
}
}
public class Shared {
// store an instance of our object in a variable
public static final Shared instance = new Shared();
// member variables pointing to two objects on the heap
public Integer object2 = new Integer(22);
public Integer object4 = new Integer(44);
}
Die Methode run() ruft die Methode one() auf und one() wiederum ruft two() auf .
Die Methode one() deklariert eine primitive lokale Variable (localOne) vom Typ int und eine lokale Variable (localTwo), was eine Referenz auf ein Objekt ist.
Jeder Thread, der die one()- Methode ausführt , erstellt seine eigene KopielocalOneUndlocalTwoin deinem Stapel. VariablenlocalOnewerden vollständig voneinander getrennt sein und sich auf dem Stapel jedes Threads befinden. Ein Thread kann nicht sehen, welche Änderungen ein anderer Thread an seiner Kopie vornimmtlocalOne.
Jeder Thread, der die one()- Methode ausführt, erstellt auch seine eigene KopielocalTwo. Allerdings zwei unterschiedliche ExemplarelocalTwozeigen am Ende auf dasselbe Objekt auf dem Heap. Die Sache ist dielocalTwozeigt auf das Objekt, auf das die statische Variable verweistBeispiel. Es gibt nur eine Kopie einer statischen Variablen, und diese Kopie wird auf dem Heap gespeichert.
Also beide ExemplarelocalTwoverweisen am Ende auf dieselbe Shared- Instanz . Die Shared- Instanz wird ebenfalls auf dem Heap gespeichert. Es passtObjekt 3im Diagramm oben.
Beachten Sie, dass die Shared- Klasse auch zwei Mitgliedsvariablen enthält. Die Mitgliedsvariablen selbst werden zusammen mit dem Objekt auf dem Heap gespeichert. Zwei Mitgliedsvariablen verweisen auf zwei andere ObjekteGanze Zahl. Diese ganzzahligen Objekte entsprechenObjekt 2UndObjekt 4auf dem Diagramm.
Beachten Sie außerdem, dass die Methode two() eine lokale Variable mit dem Namen erstelltlocalOne. Diese lokale Variable ist eine Referenz auf ein Objekt vom Typ Integer . Die Methode setzt den LinklocalOneum auf eine neue Integer- Instanz zu zeigen . Der Link wird in seiner Kopie gespeichertlocalOnefür jeden Thread. Auf dem Heap werden zwei Integer- Instanzen gespeichert. Da die Methode bei jeder Ausführung ein neues Integer- Objekt erstellt, erstellen die beiden Threads, die diese Methode ausführen, separate Integer -Instanzen . Sie passenObjekt 1UndObjekt 5im Diagramm oben.
Beachten Sie auch die beiden Mitgliedsvariablen in der Shared- Klasse vom Typ Integer , bei dem es sich um einen primitiven Typ handelt. Da es sich bei diesen Variablen um Mitgliedsvariablen handelt, werden sie weiterhin zusammen mit dem Objekt auf dem Heap gespeichert. Auf dem Thread-Stack werden nur lokale Variablen gespeichert.
GO TO FULL VERSION