Heim >Java >javaLernprogramm >Detaillierte grafische Erläuterung des JMM-Java-Speichermodells
JMM ist ein unvermeidliches Niveau für einen Programmierer, der ein tiefes Verständnis von Java erlangen möchte. Dieser Artikel ist eher theoretisch und so einfach wie möglich zu verstehen. Ich hoffe, Sie können mich korrigieren.
Dann sprechen wir zuerst über die Hauptspeicherzuweisung von JVM
1 Java Virtual Machine Stack (Java Virtual Stack )
Der Stapel der virtuellen Maschine ist für den Thread privat. Jeder Thread verfügt über einen eigenen Stapel der virtuellen Maschine, der das Speichermodell für die Ausführung jeder Methode darstellt , wird ein Stapelrahmen auf dem Stapel der virtuellen Maschine erstellt. Der Stapelrahmen ist eine Datenstruktur, die hauptsächlich die lokale -Variable (Basistyp, Referenz des -Objekts , ) speichert Adresstyp (zeigt auf die Adresse einer Bytecode-Anweisung)), Operationsstapel (bezieht sich auf den Stapel von Operationsanweisungen nach der Methodenkompilierung), dynamische Verknüpfung, Methodenausgang. Im Allgemeinen ist der Java-Speicher in Stapel und Heap unterteilt, und der Stapel bezieht sich auf den Stapel der virtuellen Maschine. Aber die Speicherzuweisung von Java ist nicht so einfach. Dynamische Verknüpfung wird wie folgt erklärt:
Jeder Stack-Frame enthält eine Ausführungslaufzeit-
KonstanteReferenz auf die Methode, zu der der Stack-Frame im Pool gehört Wird zur Unterstützung von Dynamic Linking während des Methodenaufrufs gehalten. Eine große Anzahl von Symbolreferenzen ist in der Datei
Klassegespeichert. Die Methodenaufrufanweisungen im Bytecode verwenden die Symbolreferenz, die auf die Methode im Konstantenpool verweist, als Parameter. Einige dieser Symbolreferenzen werden während der Klassenladephase oder bei der ersten Verwendung in direkte Referenzen umgewandelt. Diese Konvertierung wird als statisches Parsen bezeichnet. Der andere Teil wird bei jedem Lauf in eine direkte Referenz umgewandelt, dieser Teil wird dynamischer Link genannt. Die Erklärung des Methodenexits lautet wie folgt:
-Aufrufe verursacht. Die Stapeltiefe ist auch in der virtuellen Maschine begrenzt, andernfalls schreit die virtuelle Maschine, wenn es sich um einen uneingeschränkten rekursiven Aufruf handelt. Es ist unnötig zu erwähnen, dass OOM auftritt, wenn der angeforderte Speicher größer ist als der derzeit vom Stapel der virtuellen Maschine gehaltene Speicher (der Stapelspeicherplatz der virtuellen Maschine kann dynamisch erweitert werden, aber der der JVM zugewiesene Speicher ist ebenfalls begrenzt, sodass der Stapel der virtuellen Maschine ist nicht unendlich erweiterbar) von).
2 Lokaler MethodenstapelDer lokale Methodenstapel und der Stapel der virtuellen Maschine sind grundsätzlich ähnlich, mit der Ausnahme, dass der Stapel der virtuellen Maschine Klassenbytecode ausführt, während der Stapel der lokalen Methode Klassenbytecode ausführt Was im Methodenstapel ausgeführt wird, ist der Dienst der lokalen Methode, der tatsächlich verschiedene Implementierungen derselben Methode aufruft, die je nach Betriebssystemplattform in C oder C++ geschrieben sind.
3 Methodenbereich
Der Methodenbereich ist ein von Threads gemeinsam genutzter Bereich und wird zum Speichern von Klasseninformationen verwendet, die von der virtuellen Maschine geladen wurden (
Klasse). Bei Bytecode-Daten ist hier zu beachten, dass beim gleichzeitigen Laden vieler Klassen der Platz im Methodenbereich vergrößert werden muss, da es sonst zu OOM kommt. Dies ist nur möglich, wenn nur wenige Klassen vorhanden sind. Wenn zu viele Klassen vorhanden sind, können Sie das verzögerte Laden verwenden. Versuchen Sie, das Laden zu vieler Klassen (🎜>), Konstanten, statischen Variablen und vom Just-in-Time-Compiler (JIT) kompilierten Code zu vermeiden andere Daten gleichzeitig. Der Methodenbereich ist eigentlich das, was wir als permanenten Generierungsbereich bezeichnen ( ist auf den Implementierungsmechanismus der virtuellen Hotspot-Maschine beschränkt ). Der Grund, warum er als permanente Generierung bezeichnet wird, ist, dass die Daten hier selten durch Müll gesammelt werden Aufgrund der Belastung werden Klassen nicht in kurzer Zeit aussterben. Viele Methoden erstellen Objekte im Heap basierend auf der Klasse. Statische Variablen sind im Allgemeinen die Wurzelknoten von GC- und Suchalgorithmen, während sich Konstanten überhaupt nicht ändern . Die Daten werden selten bereinigt. Die Java-Virtual-Machine-Spezifikation weist in diesem Bereich auch sehr lockere Einschränkungen auf. Sie ist nicht nur ein physisch diskontinuierlicher Raum, sondern ermöglicht auch eine feste Größe und Skalierbarkeit und erfordert keine Implementierung der Speicherbereinigung. Relativ gesehen ist das Verhalten der Speicherbereinigung in diesem Bereich relativ selten (daher sollte der Definition von Konstanten und statischen Variablen mehr Aufmerksamkeit geschenkt werden). Die Speichersammlung im Methodenbereich erfolgt weiterhin, die Speichersammlung in diesem Bereich dient jedoch hauptsächlich der Wiederverwendung des Konstantenpools und dem Entladen von Typen. Im Allgemeinen ist das Speicherrecycling im Methodenbereich schwer zu erfüllen. Wenn der Methodenbereich die Speicherzuweisungsanforderungen nicht erfüllen kann, wird eine OutOfMemoryError-Ausnahme ausgelöst. 4 Laufzeitkonstantenpool Vor JDK1.6StringDer Konstantenpool befindet sich im Methodenbereich. Java ist eine dynamisch verbundene Sprache. Zusätzlich zu den verschiedenen im Code definierten Grundtypen (z. B. int , long usw.) und Objekttypen (wie String und Array) enthalten auch einige Symbolverweise in Textform, wie zum Beispiel: Vollqualifizierte Namen von Klassen und Schnittstellen; Feldnamen und Deskriptoren Methoden und Namen und Deskriptoren. Wenn in der C-Sprache ein Programm eine Funktion in einer anderen Bibliothek aufrufen möchte, wird beim Verbinden die Position der Funktion in der Bibliothek (d. h. relativ) angegeben zur Bibliothek Der Offset am Anfang der Datei) wird zur Laufzeit direkt an dieser Adresse aufgerufen In der Java-Sprache ist alles dynamisch. Wenn beim Kompilieren ein Aufruf anderer Klassenmethoden oder ein Verweis auf andere Klassenfelder gefunden wird, kann es sich bei dem, was in der Klassendatei aufgezeichnet wird, nur um einen Symbolverweis in Textform handeln. Während des Verbindungsprozesses sucht die virtuelle Maschine anhand dieses Textes Informationen. Die entsprechende Methode oder das entsprechende Feld. Daher ist der Inhalt der „Konstanten“ in der Klassendatei im Gegensatz zu den sogenannten „Konstanten“ in der Java-Sprache sehr umfangreich. Diese Konstanten sind in einem Bereich in der Klasse nacheinander konzentriert , hier werden sie als „konstanter Pool“ bezeichnet. Die konstante Pool-Technologie in Java scheint bestimmte Objekte bequem und schnell zu erstellen. Wenn ein Objekt benötigt wird, kann es aus dem Pool entnommen werden (falls keins im Pool vorhanden ist, erstellen Sie eines). spart viel Zeit, wenn Sie wiederholt gleiche Variablen erstellen müssen. Der Konstantenpool ist eigentlich ein Speicherbereich, der sich vom Heap-Bereich unterscheidet, in dem sich Objekte befinden, die mit dem Schlüsselwort new erstellt wurden. Der gesamte Konstantenpool wird durch einen Index der JVM referenziert. So wie auf die Menge der Elemente im Array entsprechend dem Index zugegriffen wird, verarbeitet die JVM auch die in diesen Konstantenpools gespeicherten Informationen entsprechend Indexmethode. Tatsächlich spielt der Konstantenpool im Java-Programm eine entscheidende Rolle (siehe oben). Zusätzlich zur Klassenversion, Feldern, Methoden, Schnittstellen und anderen Informationen enthält die Klassendatei auch Informationen darüber, dass der Konstantenpool zum Speichern verschiedener vom Compiler generierter Literale und Symbolreferenzen verwendet wird Die Informationen werden nach dem Laden der Klasse im Laufzeitkonstantenpool im Methodenbereich gespeichert. Die virtuelle Java-Maschine unterliegt strengen Vorschriften für jeden Teil der Klasse (einschließlich des Konstantenpools), um Spezifikationsanforderungen für die Art der Daten zu erfüllen, die für die Speicherung jedes Bytes gelten, damit sie von der virtuellen Maschine erkannt, geladen und ausgeführt werden können. Im Allgemeinen werden zusätzlich zum Speichern der in der Klassendatei beschriebenen Symbolreferenzen auch die übersetzten direkten Referenzen im Laufzeitkonstantenpool gespeichert. Ein weiteres wichtiges Merkmal des Laufzeitkonstantenpools im Vergleich zum Klassendateikonstantenpool ist, dass er dynamisch ist. Die Java Virtual Machine erfordert nicht, dass Konstanten nur während der Kompilierung generiert werden können, das heißt, sie sind nicht vorab -Eingebaut in den Konstantenpool der Klassendatei. Während der Ausführung können auch neue Konstanten in den Konstantenpool der Laufzeit eingefügt werden. Der Konstantenpool ist Teil des Methodenbereichs und daher durch den Speicher begrenzt. Wenn er nicht genügend Speicher beantragen kann, wird eine OutOfMemoryError-Ausnahme ausgelöst Der Heap ist der größte Bereich im Speicher und der einzige Ort, an dem Objektinstanzen gespeichert werden. Dieser Ort ist auch das Hauptschlachtfeld des GC-Algorithmus. Mit der Entwicklung von JIT (Just-in-Time-Kompilierung) und der Reife der Escape-Technologie werden jedoch nicht alle Objekte auf dem Heap erstellt. Das Folgende ist ein Auszug aus „Ausführliches Verständnis der Java Virtual Machine“. In der Java-Programmiersprache und -umgebung ist ein Just-in-Time-Compiler (JIT-Compiler) ein Programm, das Java-Bytecode (einschließlich Anweisungen, die interpretiert werden müssen) konvertiert. Ein Programm, das Anweisungen in umwandelt Anweisungen, die direkt an den Prozessor gesendet werden können. Wenn Sie ein Java-Programm schreiben, werden die Quellsprachenanweisungen vom Java-Compiler in Bytecode kompiliert und nicht in Anweisungscode, der einer bestimmten Prozessor-Hardwareplattform entspricht (z. B. Intels Pentium-Mikroprozessor oder IBMs System/390-Prozessor). Bytecode ist plattformunabhängiger Code, der an jede Plattform gesendet und auf dieser Plattform ausgeführt werden kann. Die Speicherzuordnung von Java ist ungefähr so. Der JVM ist auch mit vielen Parametern ausgestattet, um die oben genannten Daten anzupassen. Ich werde sie hier nicht auflisten, sondern in einem separaten Artikel zum Thema GC ausführlich erläutern. Lassen Sie uns darüber sprechen, wie JVM die durch Parallelität im Zeitalter von Mehrkernprozessoren verursachten Probleme bewältigt. Parallelitätskontrolle Eine Multi-Core-CPU kann mehrere Threads gleichzeitig ausführen, und jeder Thread verfügt über seinen eigenen lokalen Arbeitsbereich ( Tatsächlich ist es ist der System--Cache und die jedem Kern zugeordneten Register), der die aus dem Hauptspeicher oben erhaltenen Daten als Kopie speichert, um sie im Arbeitsbereich auszuführen, wenn die Daten von mehreren Threads gemeinsam genutzt werden Der Datenaustausch kann nicht durchgeführt werden, was das Problem inkonsistenter Daten in gemeinsam genutzten Variablen mit sich bringt. Java steuert die Sichtbarkeit gemeinsam genutzter Variablen durch Mechanismen wie synchronisierte flüchtige Sperren. Synchronized und Lock werden jeweils separate Kapitel zur Erläuterung der Implementierungsmechanismen haben. Es versteht sich von selbst, dass diese beiden in Bezug auf Sichtbarkeit und Atomizität garantiert sind. Nur wenn die Daten gelesen und geladen werden, werden die Datenänderungen im aktuellen Thread erkannt. Wenn Sie diese beiden Phasen bestehen, können Sie nur Mitleid mit den Daten haben. Tatsächlich vermeidet Volatile die Verwendung von Cache und speichert die Daten nicht im Hauptspeicher im Thread-Arbeitsspeicher. In den Lese- und Ladephasen werden die Daten aus dem Hauptspeicher abgerufen, damit andere Threads sie erkennen können die Änderung von Variablen ). Aufgrund der schlechten Leistung der Synchronisierung in frühen JDK-Versionen ist Volatile lediglich eine Lösung zur Gewährleistung der Sichtbarkeit. Die aktuelle JDK-Version von „Synchronized“ und „Lock“ wurde bis zu einem gewissen Grad optimiert. Daher wird im Allgemeinen nicht empfohlen, flüchtige Variablen zu verwenden, es sei denn, Sie wissen, wofür Sie derzeit flüchtige Variablen verwenden, da dies die Korrektheit der Parallelität nicht garantiert. Variablen aus dem Hauptspeicher lesen und laden, in den aktuellen Arbeitsspeicher kopieren, Ausführungscode verwenden und zuweisen, gemeinsam genutzte Variablenwerte ändern, Arbeitsspeicherdaten speichern und schreiben, um den Hauptspeicher zu aktualisieren Speicherbezogener Inhalt, bei dem „Use“ und „Assign“ mehrfach vorkommen können. volatile eignet sich für einige idempotente Operationen. Dies wird in der Implementierung von lock's nofairsync erläutert. An dieser Stelle muss ich über das Prinzip des Geschehens vorher sprechen. Es handelt sich um eine Teilordnungsbeziehung zwischen zwei im Java-Speichermodell definierten Operationen. Wenn Operation A vor Operation B auftritt, bedeutet dies, dass die Auswirkung von Operation A durch Operation B beobachtet werden kann. „Einfluss“ umfasst Änderungen der Wert gemeinsam genutzter Variablen im Speicher, das Senden von Nachrichten, das Aufrufen von Methoden usw. Es hat im Grunde wenig mit der zeitlichen Abfolge von Ereignissen zu tun. Dieses Prinzip ist besonders wichtig. Es ist die Hauptgrundlage für die Beurteilung, ob in den Daten Konkurrenz besteht und ob der Thread sicher ist. Im Folgenden sind acht Regeln im Java-Speichermodell aufgeführt, die ein Vorhergehen garantieren. Sie existieren bereits ohne Synchronisierungsunterstützung und können direkt in der Codierung verwendet werden. Wenn die Beziehung zwischen zwei Vorgängen nicht in dieser Liste enthalten ist und nicht aus den folgenden Regeln abgeleitet werden kann, gibt es für sie keine Reihenfolgengarantie, und die virtuelle Maschine kann sie nach dem Zufallsprinzip neu anordnen (um die CPU vollständig auszunutzen und die Auslastung zu verbessern, die JVM, JVM). ordnet irrelevante Codes oder Vorgänge neu an, sodass Vorgänge, die auf E/A oder andere Ressourcen warten müssen, hinten platziert werden, während andere Vorgänge, die sofort abgeschlossen werden können, vorne platziert und zuerst ausgeführt werden, wodurch die CPU-Ressourcen vollständig genutzt werden. 1. Programmsequenzregeln: In einem separaten Thread findet entsprechend der Ausführungsflusssequenz des Programmcodes die zuerst (zeitlich) ausgeführte Operation statt – vor (zeitlich) der später ausgeführten Operation. 2. Management-Sperrregeln: Ein Entsperrvorgang findet statt – vor (in der zeitlichen Reihenfolge, das gleiche unten) einem Sperrvorgang für dasselbe Schloss. 3. Regeln für flüchtigeVariablen: Ein Schreibvorgang für eine flüchtige Variable erfolgt – vor einem nachfolgenden Lesevorgang für die Variable. 4. Thread-Startregeln: Die start()-Methode des Thread-Objekts wird ausgeführt – vor jeder Aktion dieses Threads. 5. Thread-Beendigungsregeln: Alle Vorgänge des Threads finden statt – vor der Beendigungserkennung dieses Threads kann der Thread über das Ende der Thread.join()-Methode, den Rückgabewert von Thread, erkannt werden. isAlive() usw. Die Ausführung wurde abgebrochen. 6. Thread-Unterbrechungsregeln: Der Aufruf der Thread-Interrupt()-Methode erfolgt – bevor das Ereignis auftritt, wenn der Code des unterbrochenen Threads die Unterbrechung erkennt. 7. Objekt-Finalisierungsregeln: Die Initialisierung eines Objekts ist abgeschlossen ( Konstruktor Ausführung endet) und zwar vor dem Start seiner finalize()-Methode. 8. Transitivität: Wenn Operation A geschieht – vor Operation B und Operation B – vor Operation C, dann kann daraus geschlossen werden, dass A geschieht – vor Operation C.
Der JDK1.7-String-Konstantenpool wurde auf den Heap verschoben.
Das obige ist der detaillierte Inhalt vonDetaillierte grafische Erläuterung des JMM-Java-Speichermodells. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!