1. Wenn Sie von einer Java Virtual Machine sprechen, meinen Sie möglicherweise:
1. Eine spezifische Java Virtual Machine-Implementierung virtuelle Maschine. Maschineninstanz
2. Lebenszyklus der Java Virtual Machine
Eine laufende Java Virtual Machine hat eine klare Aufgabe: Java-Programme ausführen. Es wird ausgeführt, wenn die Ausführung des Programms beginnt, und stoppt, wenn das Programm endet. Wenn Sie drei Programme auf derselben Maschine ausführen, werden drei virtuelle Java-Maschinen ausgeführt.
Die Java Virtual Machine startet immer mit einer main()-Methode. Diese Methode muss öffentlich sein, void zurückgeben und direkt ein String-Array akzeptieren. Wenn das Programm ausgeführt wird, müssen Sie den Klassennamen angeben, der die main()-Methode in die Java Virtual Machine umschließt. Die Methode
Main() ist der Startpunkt des Programms und der Thread, in dem sie ausgeführt wird, wird als Anfangsthread des Programms initialisiert. Alle anderen Threads im Programm werden von ihm gestartet. In Java gibt es zwei Arten von Threads: Daemon-Threads und Nicht-Daemon-Threads. Der Daemon-Thread ist ein Thread, der von der Java Virtual Machine selbst verwendet wird. Der Thread, der für die Speicherbereinigung verantwortlich ist, ist beispielsweise ein Daemon-Thread. Natürlich können Sie auch Ihr eigenes Programm als Daemon-Thread festlegen. Der anfängliche Thread, der die Main()-Methode enthält, ist kein Daemon-Thread.
Solange in der Java Virtual Machine normale Threads ausgeführt werden, wird die Java Virtual Machine nicht angehalten. Wenn Sie über ausreichende Berechtigungen verfügen, können Sie das Programm beenden, indem Sie die Methode „exit()“ aufrufen.
3. Architektur der Java Virtual Machine
Eine Reihe von Subsystemen, Speicherbereichen, Datentypen und Nutzungsrichtlinien sind in den Spezifikationen der Java Virtual Machine definiert. Diese Komponenten bilden die interne Struktur der Java Virtual Machine. Sie stellen nicht nur eine klare interne Struktur für die Implementierung der Java Virtual Machine bereit, sondern regeln auch streng das externe Verhalten der Java Virtual Machine.
Jede Java Virtual Machine verfügt über ein Klassenlader-Subsystem (Klassenlader-Subsystem), das dafür verantwortlich ist, Typen (Klassen und Schnittstellen) im Programm zu laden und ihnen eindeutige Namen zu geben. Jede virtuelle Java-Maschine verfügt über eine Ausführungs-Engine (Ausführungs-Engine), die für die Ausführung von Anweisungen verantwortlich ist, die in geladenen Klassen enthalten sind.
Die Ausführung des Programms erfordert eine gewisse Menge an Speicherplatz, wie z. B. Bytecode, andere zusätzliche Informationen der geladenen Klasse, Objekte im Programm, Methodenparameter, Rückgabewerte, lokale Variablen, verarbeitete Zwischenvariablen usw. Die Java Virtual Machine speichert alle diese Informationen in Datenbereichen. Obwohl jede Java Virtual Machine-Implementierung einen Datenbereich umfasst, sind die Bestimmungen der Java Virtual Machine-Spezifikation für den Datenbereich sehr abstrakt. Viele strukturelle Details bleiben den Implementierern der Java Virtual Machine überlassen. Die Speicherstrukturen verschiedener Java Virtual Machine-Implementierungen variieren stark. Einige Implementierungen verbrauchen möglicherweise viel Speicher, während andere möglicherweise nur sehr wenig Speicher verwenden. Einige Implementierungen verwenden möglicherweise virtuellen Speicher, andere nicht. Diese relativ verfeinerte Speicherspezifikation der Java Virtual Machine ermöglicht die Implementierung der Java Virtual Machine auf einer Vielzahl von Plattformen.
Ein Teil des Datenbereichs gilt für das gesamte Programm, andere Teile werden von separaten Threads gesteuert. Jede Java Virtual Machine enthält einen Methodenbereich und einen Heap, die vom gesamten Programm gemeinsam genutzt werden. Nachdem die Java Virtual Machine eine Klasse geladen und analysiert hat, speichert sie die aus der Klassendatei analysierten Informationen im Methodenbereich. Objekte, die bei der Ausführung des Programms erstellt werden, werden im Heap gespeichert.
Wenn ein Thread erstellt wird, wird ihm ein eigenes PC-Register „PC-Register“ (Programmzähler) und ein eigener Java-Stack (Java-Stack) zugewiesen. Wenn der Thread nicht die native Methode verwendet, speichert das PC-Register die nächste vom Thread ausgeführte Anweisung. Der Java-Stack speichert den Status, wenn ein Thread eine Methode aufruft, einschließlich lokaler Variablen, Parameter der aufrufenden Methode, Rückgabewerte und verarbeiteter Zwischenvariablen. Der Status beim Aufruf einer nativen Methode wird in nativen Methodenstapeln gespeichert, möglicherweise in Registern oder einem anderen nicht plattformunabhängigen Speicher.
Der Java-Stack besteht aus Stack-Frames (oder Frames). Der Stack-Block enthält den Status von Java-Methodenaufrufen. Wenn ein Thread eine Methode aufruft, schiebt die Java Virtual Machine einen neuen Block auf den Java-Stack. Wenn die Methode endet, entnimmt die Java Virtual Machine den entsprechenden Block und verwirft ihn.
Die Java Virtual Machine verwendet keine Register zum Speichern von Zwischenergebnissen von Berechnungen, sondern den Java-Stack zum Speichern von Zwischenergebnissen. Dies macht die Java Virtual Machine-Anweisungen kompakter und erleichtert die Implementierung der Java Virtual Machine auf einem Gerät ohne Register.
Im Java-Stack im Bild ist Thread drei im PC-Register grau, da er nach unten wächst, weil er eine lokale Methode ausführt und seine nächste Ausführungsanweisung nicht im PC-Register gespeichert wird.
4. Datentypen
Alle in der Java Virtual Machine verwendeten Daten haben einen bestimmten Datentyp. Datentypen und Operationen sind in der Java Virtual Machine-Spezifikation streng definiert. Datentypen in Java werden in primitive Typen (Primitivtypen) und Referenzdatentypen (Referenztypen) unterteilt. Referenztypen hängen vom tatsächlichen Objekt ab, nicht jedoch vom Objekt selbst. Primitive Datentypen hängen von nichts ab; sie repräsentieren die Daten selbst.
Alle primitiven Datentypen in der Programmiersprache Java sind primitive Datentypen der Java Virtual Machine, mit Ausnahme von Boolean. Wenn der Compiler Java-Quellcode in seinen eigenen Code kompiliert, verwendet er den Ganzzahltyp (int) oder den Bytetyp (byte), um den booleschen Typ darzustellen. Die Java Virtual Machine verwendet die Ganzzahl 0 zur Darstellung des booleschen Werts „false“ und eine Ganzzahl ungleich Null zur Darstellung des booleschen Werts „true“.
Mit Ausnahme von Boolean sind andere primitive Typen in der Java-Sprache Datentypen in der Java Virtual Machine. In Java werden Datentypen unterteilt in: Ganzzahlbyte, kurz, int, lang; char und Gleitkommatyp float, double. Datentypen in der Java-Sprache haben auf jedem Host den gleichen Gültigkeitsbereich.
In der Java Virtual Machine gibt es auch einen primitiven Datentyp-Rückgabewerttyp (returnValue), der in der Java-Sprache nicht verwendet werden kann. Dieser Typ wird verwendet, um „finally-Klauseln“ in Java-Programmen zu implementieren. Weitere Informationen finden Sie unter „Finally-Klauseln“ in Kapitel 18.
Referenztypen können erstellt werden als: Klassentyp, Schnittstellentyp, Array-Typ. Sie beziehen sich alle auf dynamisch erstellte Objekte. Wenn ein Referenztyp auf Null verweist, bedeutet dies, dass kein Objekt referenziert wird.
Die Java Virtual Machine-Spezifikation definiert nur den Bereich, der von jedem Datentyp dargestellt wird, definiert jedoch nicht den Speicherplatz, den jeder Typ während der Speicherung einnimmt. Wie sie gespeichert werden, bleibt dem Implementierer der Java Virtual Machine überlassen. Weitere Informationen zu Gleitkommatypen finden Sie in Kapitel 14, „Gleitkomma-Arithmetik“.
TypeRange
Byte8-Bit-Zweierkomplement-Ganzzahl mit Vorzeichen (-27 bis 27 - 1, einschließlich)
short16-Bit-Zweierkomplement-Ganzzahl mit Vorzeichen (-215 bis 215 - 1, einschließlich)
int32-Bit-Zweierkomplement-Ganzzahl mit Vorzeichen (-231 bis 231 - 1, einschließlich)
long64-Bit-Zweierkomplement-Ganzzahl mit Vorzeichen (-263 bis 263 - 1, einschließlich)
char16-Bit-Unicode-Zeichen ohne Vorzeichen (0 bis 216 - 1, einschließlich)
float32-Bit-IEEE 754-Float mit einfacher Genauigkeit
double64-bit IEEE 754 double-precision float
returnValueAdresse eines Opcodes innerhalb derselben Methode
ReferenzReferenz auf ein Objekt auf dem Heap oder Null
5. Bytelänge
Das kleinste Dateneinheitswort (Wort) in der Java Virtual Machine, deren Größe vom Implementierer der Java Virtual Machine definiert wird. Die Größe eines Wortes muss jedoch ausreichen, um Byte, Short, Int, Char, Float und ReturnValue aufzunehmen. Zwei Wörter müssen ausreichen, um Long und Double aufzunehmen. Daher muss der Implementierer der virtuellen Maschine mindestens ein Wort bereitstellen, das kleiner als 31 Bit ist. Es ist jedoch am besten, die effizienteste Wortlänge auf der jeweiligen Plattform zu wählen.
Zur Laufzeit kann ein Java-Programm die Wortlänge der Maschine, auf der es läuft, nicht ermitteln. Die Wortlänge hat keinen Einfluss auf das Verhalten des Programms, sie ist lediglich eine Ausdrucksweise in der Java Virtual Machine.
6. Klassenlader-Subsystem
Es gibt zwei Arten von Klassenladern in der Java Virtual Machine: ursprüngliche Klassenlader und Klassenladerobjekte. Der ursprüngliche Klassenlader ist Teil der Java Virtual Machine-Implementierung, und das Klassenladerobjekt ist Teil des laufenden Programms. Von verschiedenen Klassenladern geladene Klassen werden durch unterschiedliche Namespaces getrennt.
Der Klassenlader ruft viele andere Teile der Java Virtual Machine und viele Klassen im java.lang-Paket auf. Beispielsweise ist das Klassenladeobjekt eine Instanz der Unterklasse java.lang.ClassLoader. Die Methoden in der ClassLoader-Klasse können auf den Klassenlademechanismus in der virtuellen Maschine zugreifen und werden als Java dargestellt .lang.Class Instanz der Klasse. Klassenladeobjekte und Klassenobjekte werden wie andere Objekte im Heap gespeichert, und geladene Informationen werden im Methodenbereich gespeichert.
1. Laden, Verknüpfen und Initialisieren
Das Klassenlade-Subsystem ist nicht nur für das Auffinden und Laden von Klassendateien verantwortlich, sondern führt auch viele andere Dinge gemäß den folgenden strengen Schritten aus: (Spezifische Informationen finden Sie in Kapitel 7 „Klassenlebenszyklus“).
1), Laden: Binärinformationen des angegebenen Typs (Klasse und Schnittstelle) finden und importieren
2), Verbindung: Überprüfen, vorbereiten und analysieren
①Überprüfung: Sicherstellen, dass der importierte Typ korrekt ist
②Vorbereitung: Speicher für den Typ zuweisen und Initialisieren Sie es auf den Standardwert.
③Parsen: Analysieren Sie die Zeichenreferenz, um direkt zu trinken.
3), Initialisierung: Rufen Sie den Java-Code auf und initialisieren Sie die Klassenvariable auf den entsprechenden Wert. 2. Der ursprüngliche Klassenlader ein primitiver Klassenlader, der Klassen laden kann, die dem Klassendateiformat entsprechen und vertrauenswürdig sind. Die Spezifikation der Java Virtual Machine definiert jedoch nicht, wie Klassen geladen werden, die Entscheidung bleibt dem Implementierer der Java Virtual Machine überlassen. Für einen Typ mit einem bestimmten Typnamen muss der ursprüngliche Lader die Datei mit dem Typnamen plus „.class“ finden und sie in die virtuelle Maschine laden.
3. Klassenladeobjekt
Obwohl das Klassenladeobjekt Teil des Java-Programms ist, können die drei Methoden in der ClassLoader-Klasse auf das Klassenladesubsystem in der Java Virtual Machine zugreifen.
1), geschützte letzte Klasse defineClass(...): Verwenden Sie diese Methode, um auf ein Byte-Array zuzugreifen und einen neuen Typ zu definieren.
2), protected Class findSystemClass(String name): Laden Sie die angegebene Klasse. Wenn sie geladen wurde, kehren Sie direkt zurück.
3), protected final void discoverClass(Class c): Die Methode defineClass() lädt lediglich eine Klasse. Diese Methode ist für die anschließende dynamische Verbindung und Initialisierung verantwortlich.
Weitere Informationen finden Sie in Kapitel 8 „Das Verknüpfungsmodell“.
4. Namespace
Wenn mehrere Klassenlader dieselbe Klasse laden, muss vor dem Klassennamen die Kennung des Klassenladers hinzugefügt werden, der die Klasse lädt, um die Eindeutigkeit ihrer Namen sicherzustellen. Spezifische Informationen finden Sie in Kapitel 8 „Das Verknüpfungsmodell“.
7. Der Methodenbereich
In der Java Virtual Machine werden die Informationen des geladenen Typs im Methodenbereich gespeichert. Die Organisationsform dieser Informationen im Speicher wird vom Implementierer der virtuellen Maschine definiert. Wenn die virtuelle Maschine beispielsweise auf einem „Little-Endian“-Prozessor arbeitet, kann sie die Informationen zwar im „Little-Endian“-Format speichern In Java-Klassendateien werden sie im „Big-Endian“-Format gespeichert. Designer können Daten in einem Darstellungsformat speichern, das für den lokalen Computer am besten geeignet ist, um sicherzustellen, dass das Programm mit der schnellsten Geschwindigkeit ausgeführt werden kann. Auf einem Gerät mit nur wenig Speicher belegt der Implementierer der virtuellen Maschine jedoch nicht viel Speicher.
Alle Threads im Programm teilen sich einen Methodenbereich, daher muss die Methode zum Zugriff auf Methodenbereichsinformationen threadsicher sein. Wenn zwei Threads eine Klasse namens Lava laden, darf nur ein Thread diese Klasse laden und der andere muss warten.
Wenn das Programm ausgeführt wird, ist die Größe des Methodenbereichs variabel und das Programm kann während der Programmausführung erweitert werden. Einige Java Virtual Machine-Implementierungen können auch die Anfangsgröße sowie die Mindest- und Höchstwerte des Methodenbereichs über Parameter anpassen.
Der Methodenbereich kann auch durch Müll gesammelt werden. Da der Inhalt des Programms dynamisch vom Klassenlader geladen wird, kann es sein, dass alle Klassen nicht mehr referenziert werden. Wenn eine Klasse diesen Zustand erreicht, wird sie möglicherweise durch Garbage Collection erfasst. Nicht geladene Klassen enthalten zwei Zustände, einer ist wirklich nicht geladen und der andere ist der „nicht referenzierte“ Zustand. Weitere Informationen finden Sie unter „Die Lebensdauer einer Klasse“ in Kapitel 7.
1. Typinformationen
Jeder geladene Typ speichert die folgenden Informationen im Methodenbereich der Java Virtual Machine:
1), Der vollständig qualifizierte Name des Typs
2), der vollständig qualifizierte Name der direkten Superklasse des Typs (sofern nicht Es gibt keinen übergeordneten Typ oder die Frey-Form ist java.lang.Object) (Der vollständig qualifizierte Name der direkten Superklasse des Typs)
3), ob der angegebene Typ eine Klasse oder Schnittstelle (Klasse oder Schnittstelle) ist (Ob oder nicht der Typ ist eine Klasse)
4), Typmodifikatoren (öffentlich, privat, geschützt, statisch, endgültig, flüchtig, transient usw.) (Modifikatoren des Typs)
5), alle übergeordneten Schnittstellen Eine geordnete Liste der vollständig qualifizierten Namen aller direkten Superschnittstellen. Die durch den vollständigen Namen des Typs gespeicherte Datenstruktur wird vom Implementierer der virtuellen Maschine definiert. Darüber hinaus speichert die Java Virtual Machine auch die folgenden Informationen für jeden Typ:
1), den Konstantenpool für den Typ (Der Konstantenpool für den Typ)
2), Typfeldinformationen (Feldinformationen)
3), Typ Methodeninformationen (Methodeninformationen)
4), alle im Typ deklarierten (statischen) Klassenvariablen, außer Konstanten (Alle im Typ deklarierten (statischen) Klassenvariablen, außer Konstanten)
5), ein Verweis auf den Klassenlader (A Verweis auf die Klasse ClassLoader)
6), ein Verweis auf die Klasse Class (ein Verweis auf die Klasse Class)
1) Der Konstantenpool für den Typ (Der Konstantenpool für den Typ)
Alle im Konstantenpool gespeicherten Typen sind geordnete Sammlungen von Konstanten, einschließlich direkter Konstanten (Literale) wie Zeichenfolgen, Ganzzahlen und Gleitkommakonstanten symbolische Verweise auf Typen, Felder und Methoden. Jede im Konstantenpool gespeicherte Konstante verfügt über einen Index, genau wie ein Feld in einem Array. Da der Konstantenpool Zeichenverweise auf Typen, Felder und Methoden speichert, die von allen Typen im Konstantenpool verwendet werden, ist er auch das Hauptobjekt der dynamischen Verbindung. Einzelheiten finden Sie in Kapitel 6, „Die Java-Klassendatei“.
2), Typfeldinformationen (Feldinformationen)
Feldname, Feldtyp, Feldmodifikatoren (öffentlich, privat, geschützt, statisch, endgültig, flüchtig, vorübergehend usw.) und die Reihenfolge, in der die Felder im definiert sind Klasse.
3), Typmethodeninformationen (Methodeninformationen)
Methodenname, Methodenrückgabewerttyp (oder void), Anzahl der Methodenparameter, Typen und ihre Reihenfolge, Feldmodifikatoren (öffentlich, privat, geschützt, statisch, endgültig, flüchtig, vorübergehend). usw.), die Reihenfolge, in der Methoden in der Klasse definiert sind
Wenn es nicht abstrakt und lokal ist, muss diese Methode auch den Bytecode der Methode, die Größe des Operandenstapels der Methode und die Größe von speichern der lokale Variablenbereich (einige (Details werden später bereitgestellt), Ausnahmeliste (Einzelheiten finden Sie in Kapitel 17 „Ausnahmen“)
4), Klassenvariablen (statische Variablen)
Klassenvariablen werden von allen Instanzen gemeinsam genutzt Klasse, auch wenn nicht über Instanzen der Klasse Auch zugänglich. Diese Variablen sind an die Klasse gebunden (und nicht an Instanzen der Klasse), sodass sie Teil der logischen Daten der Klasse sind. Bevor die Java Virtual Machine diese Klasse verwendet, müssen Sie Speicher für die Klassenvariable (nicht endgültig) zuweisen. Die Verarbeitungsmethode der Konstante (final) unterscheidet sich von dieser Klassenvariablen (nicht endgültig). Wenn jeder Typ eine Konstante verwendet, kopiert er diese in seinen eigenen Konstantenpool. Konstanten werden wie Klassenvariablen auch im Methodenbereich gespeichert, mit der Ausnahme, dass sie im Konstantenpool gespeichert werden. (Wahrscheinlich werden Klassenvariablen von allen Instanzen gemeinsam genutzt, während der Konstantenpool für jede Instanz einzigartig ist.) Nicht-finale Klassenvariablen werden als Teil der Daten für den Typ gespeichert, der sie deklariert, während finale Konstanten als Teil der Daten für jeden Typ gespeichert werden, der sie verwendet. Einzelheiten finden Sie in Kapitel 6 „Die Java-KlassendateiDie Java-Klassendatei“
5), Ein Verweis auf die Klasse ClassLoader
Für jeden von der Java Virtual Machine geladenen Typ muss die virtuelle Maschine speichern, ob es sich bei diesem Typ um den Original-Klassenlader oder den Klassenlader handelt Laden. Von einem Klassenlader geladene Typen müssen einen Verweis auf den Klassenlader beibehalten. Diese Informationen werden verwendet, wenn der Klassenlader eine dynamische Verbindung herstellt. Wenn eine Klasse auf eine andere Klasse verweist, muss die virtuelle Maschine speichern, dass der referenzierte Typ vom selben Klassenlader geladen wird. Dies ist auch der Prozess, bei dem die virtuelle Maschine unterschiedliche Namespaces verwaltet. Einzelheiten finden Sie in Kapitel 8 „Das Verknüpfungsmodell“
6), Ein Verweis auf die Klasse Class
Die Java Virtual Machine erstellt für jeden geladenen Typ eine Instanz der Klasse java.lang.Class. Sie können auch die Klassenmethode „Class“ verwenden:
public static Class forName(String className), um eine Klasse zu suchen oder zu laden und eine Instanz der entsprechenden Class-Klasse abzurufen. Über diese Instanz der Class-Klasse können wir auf die Informationen im Methodenbereich der Java Virtual Machine zugreifen. Einzelheiten finden Sie im JavaDoc der Class-Klasse.
2. Methodentabellen
Um effizienter auf alle im Methodenbereich gespeicherten Daten zugreifen zu können, muss die Speicherstruktur dieser Daten sorgfältig gestaltet werden. In allen Methodenbereichen gibt es neben der Speicherung der oben genannten Originalinformationen auch eine Datenstruktur, die den Zugriff beschleunigen soll, beispielsweise eine Methodenliste. Für jede geladene nicht abstrakte Klasse generiert die Java Virtual Machine eine Methodenliste für sie. Diese Liste speichert Verweise auf alle Instanzmethoden, die von dieser Klasse aufgerufen werden können, und meldet Fehler an Methoden, die in der übergeordneten Klasse aufgerufen werden. Einzelheiten finden Sie in Kapitel 8 „Das Verknüpfungsmodell“. 8. Heap
Wenn ein Java-Programm eine Instanz oder ein Array einer Klasse erstellt, reserviert es Speicher für das neue Objekt im Heap. In der virtuellen Maschine gibt es nur einen Heap, den alle Threads gemeinsam nutzen.
1. Garbage Collection
Die Garbage Collection ist die Hauptmethode zum Freigeben nicht referenzierter Objekte. Es kann auch Objekte verschieben, um die Heap-Fragmentierung zu reduzieren. Die Garbage Collection ist in der Java Virtual Machine-Spezifikation nicht streng definiert, aber es ist definiert, dass eine Java Virtual Machine-Implementierung ihren eigenen Heap auf irgendeine Weise verwalten muss. Weitere Informationen finden Sie in Kapitel 9 „Garbage Collection“.
2. Objektspeicherstruktur (Objektdarstellung)
Die Spezifikationen der Java Virtual Machine legen nicht fest, wie Objekte im Heap gespeichert werden. Jedes Objekt speichert hauptsächlich Objektvariablen, die in seiner Klasse und seiner übergeordneten Klasse definiert sind. Für einen Verweis auf ein bestimmtes Objekt muss die virtuelle Maschine die Daten dieses Objekts schnell finden. Darüber hinaus muss eine Methode bereitgestellt werden, um über das Objekt auf die Daten des Methodenobjekts zu verweisen, z. B. auf das Objekt im Methodenbereich. Daher enthalten die von einem Objekt gespeicherten Daten häufig in irgendeiner Form einen Zeiger auf den Methodenbereich.
Ein mögliches Heap-Design besteht darin, den Heap in zwei Teile zu unterteilen: einen Referenzpool und einen Objektpool. Eine Referenz auf ein Objekt ist ein lokaler Zeiger auf einen Referenzpool. Jeder Eintrag im Referenzpool enthält zwei Teile: einen Zeiger auf die Objektdaten im Objektpool und einen Zeiger auf die Objektklassendaten im Methodenbereich. Dieses Design kann die Defragmentierung des Java Virtual Machine-Heaps erleichtern. Wenn die virtuelle Maschine ein Objekt im Objektpool verschiebt, muss sie nur die Zeigeradresse im entsprechenden Referenzpool ändern. Aber jedes Mal, wenn Sie auf die Daten des Objekts zugreifen, müssen Sie den Zeiger zweimal verarbeiten. Die folgende Abbildung zeigt dieses Heap-Design. Das HeapOfFish-Applet in Kapitel 9, „Garbage Collection“, demonstriert dieses Design.
Ein anderes Heap-Design ist: Die Referenz eines Objekts ist ein Offset-Zeiger, der auf einen Datenstapel und auf das entsprechende Objekt zeigt. Dieses Design erleichtert den Objektzugriff, die Objektbewegung wird jedoch äußerst kompliziert. Die folgende Abbildung zeigt diesen Entwurf:
Wenn das Programm versucht, ein Objekt in einen anderen Typ zu konvertieren, muss die virtuelle Maschine feststellen, ob es sich bei der Konvertierung um den Typ des Objekts oder seinen übergeordneten Typ handelt. Ähnliches geschieht, wenn das Programm die Anweisung „instanceof“ verwendet. Wenn ein Programm eine Methode eines Objekts aufruft, muss die virtuelle Maschine eine dynamische Bindung durchführen und bestimmen, welche Art von Methode aufgerufen werden soll. Hierzu bedarf es auch des oben genannten Urteils.
Unabhängig davon, welches Design der Implementierer der virtuellen Maschine verwendet, kann er für jedes Objekt Informationen ähnlich einer Methodenliste speichern. Da dadurch die Geschwindigkeit von Objektmethodenaufrufen erhöht werden kann, ist es sehr wichtig, die Leistung virtueller Maschinen zu verbessern. In der Spezifikation der virtuellen Maschine ist jedoch nicht vorgeschrieben, dass ähnliche Datenstrukturen implementiert werden müssen. Das folgende Diagramm zeigt diese Struktur. Die Abbildung zeigt alle mit einer Objektreferenz verknüpften Datenstrukturen, einschließlich:
1), einen Zeiger auf Typdaten
2) und eine Methodenliste eines Objekts. Eine Methodenliste ist ein Array von Zeigern auf alle Methoden, die für ein Objekt aufgerufen werden können. Methodendaten umfassen drei Teile: die Größe des Opcode-Stapels und den lokalen Variablenbereich des Methodenstapels;
Jedes Objekt in der Java Virtual Machine muss mit einer Sperre (Mutex) verknüpft sein, die zum Synchronisieren mehrerer Threads verwendet wird. Gleichzeitig kann nur ein Objekt die Sperre dieses Objekts halten. Wenn eine Person die Sperre dieses Objekts besitzt, kann sie die Sperre mehrmals beantragen, muss die Sperre jedoch auch entsprechend oft aufheben, bevor sie die Objektsperre tatsächlich aufheben kann. Viele Objekte sind während ihrer gesamten Lebensdauer nicht gesperrt, sodass diese Informationen nur bei Bedarf hinzugefügt werden müssen. Viele Java Virtual Machine-Implementierungen enthalten keine „Sperrdaten“ in den Objektdaten und generieren die entsprechenden Daten nur bei Bedarf. Zusätzlich zur Implementierung der Objektsperre ist jedes Objekt auch logisch mit einer „Wartesatz“-Implementierung verknüpft. Durch das Sperren können Threads gemeinsam genutzte Daten unabhängig verarbeiten, ohne andere Threads zu beeinträchtigen. „Warten eingestellt“ hilft Gruppenthreads bei der Zusammenarbeit, um dasselbe Ziel zu erreichen. „Wait set“ wird häufig über die Methoden wait() und notify() der Object-Klasse implementiert.
Die Garbage Collection erfordert auch Informationen darüber, ob Objekte im Heap verknüpft sind. Die Java Virtual Machine-Spezifikation besagt, dass die Garbage Collection die Finalizer-Methode eines Objekts einmal ausführt, der Finalizer-Methode jedoch erlaubt, das Objekt erneut zu referenzieren. Wenn das Objekt nicht erneut referenziert wird, besteht keine Notwendigkeit, die Finalize-Methode erneut aufzurufen. Daher muss die virtuelle Maschine auch Informationen darüber speichern, ob die Finalize-Methode ausgeführt wurde. Weitere Informationen finden Sie unter „Garbage Collection“ in Kapitel 9
3. Speicherung von Arrays (Array-Darstellung)
In Java ist ein Array ein vollständiges Objekt. Es wird wie ein Objekt im Heap gespeichert und verfügt über einen Zeiger auf eine Referenz der Klasse A zu einer Klasseninstanz. Alle Arrays derselben Dimension und desselben Typs haben dieselbe Klasse, und die Länge des Arrays wird nicht berücksichtigt. Der der Klasse entsprechende Name wird als Dimension und Typ ausgedrückt. Beispielsweise lautet der Klassenname ganzzahliger Daten „[I“, der Klassenname eines dreidimensionalen Byte-Arrays lautet „[[[B““ und der Klassenname zweidimensionaler Objektdaten lautet „[[Ljava. lang.Object".
Mehrdimensionale Arrays werden als Arrays von Arrays dargestellt, wie unten gezeigt:
Arrays müssen die Länge des Arrays, die Daten des Arrays und einige Verweise auf Objektarray-Typdaten im Heap speichern. Durch eine Array-Referenz sollte die virtuelle Maschine in der Lage sein, die Länge eines Arrays abzurufen, durch Indizierung auf bestimmte Daten zuzugreifen und durch Object definierte Methoden aufzurufen. Object ist die direkte übergeordnete Klasse aller Datenklassen. Weitere Informationen finden Sie in Kapitel 6, „Klassendateien“.
9. PC-Register (Programmzähler) (Der Programmzähler)
Ein Programmzähler wird erstellt, wenn jeder Thread mit der Ausführung beginnt. Der Programmzähler ist nur ein Wort lang und kann daher einen lokalen Zeiger und einen Rückgabewert enthalten. Wenn der Thread ausgeführt wird, speichert der Programmzähler die Adresse des ausgeführten Befehls. Diese Adresse kann ein lokaler Zeiger oder ein Offset-Zeiger sein, der vom Bytecode der Methode ausgeht. Wenn eine native Methode ausgeführt wird, ist der Programmzählerwert nicht definiert.
10. Der Java-Stack
Wenn ein Thread startet, erstellt die Java Virtual Machine einen Java-Stack dafür. Der Java-Stack verwendet einige diskrete Frame-Klassen, um den Status von Threads aufzuzeichnen. Java Virtual Machine Heap Es gibt nur zwei Vorgänge auf dem Java-Stack: Frames verschieben und löschen.
Die im Thread ausgeführte Methode wird als aktuelle Methode bezeichnet, und der der aktuellen Methode entsprechende Frame wird als aktueller Frame bezeichnet. Die Klasse, die die aktuelle Methode definiert, wird als aktuelle Klasse bezeichnet, und der Konstantenpool der aktuellen Klasse wird als aktueller Konstantenpool bezeichnet. Wenn ein Thread ausgeführt wird, verfolgt die Java Virtual Machine die aktuelle Klasse und den aktuellen Konstantenpool. Wenn ein Thread jedoch die im Frame gespeicherten Daten verarbeitet, verarbeitet er nur die Daten des aktuellen Frames.
Wenn ein Thread eine Methode aufruft, generiert die virtuelle Maschine einen neuen Frame und schiebt ihn in den Java-Stack des Threads. Dieser neue Frame wird zum aktuellen Frame. Wenn die Methode ausgeführt wird, verwendet sie den aktuellen Frame, um die Parameter, lokalen Variablen, Zwischenstrukturen und andere Daten der Methode zu speichern. Methoden haben zwei Möglichkeiten zum Beenden: normales Beenden und abnormales Beenden. Unabhängig davon, wie die Methode gestartet wird, wird die Java Virtual Machine angezeigt und der Frame der Methode verworfen, und der Frame der vorherigen Methode wird zum aktuellen Frame.
Auf alle in einem Frame gespeicherten Daten kann nur der Thread zugreifen, der sie besitzt. Threads können nicht auf Daten in den Stapeln anderer Threads zugreifen. Daher besteht beim Zugriff auf lokale Variablen einer Methode keine Notwendigkeit, die Multithread-Synchronisierung in Betracht zu ziehen.
Wie der Methodenbereich und der Heap benötigt der Java-Stack keinen kontinuierlichen Speicherplatz. Er kann in einem verteilten Speicherbereich oder auf dem Heap gespeichert werden. Die spezifischen Daten und die Länge des Stapels werden vom Implementierer der Java Virtual Machine definiert. Einige Implementierungen stellen möglicherweise Methoden zum Durchführen einer Stapelmaximierung und -minimierung bereit.
11. Der Stapelrahmen
Der Stapelrahmen enthält drei Teile: lokale Variablen, Operandenstapel und Rahmendaten. Die Größe lokaler Variablen und Operandenstapel wird in Worten gemessen und während der Kompilierung bestimmt. Die Größe der Rahmendaten hängt von verschiedenen Implementierungen ab. Wenn ein Programm eine Methode aufruft, ermittelt die virtuelle Maschine die Größe der lokalen Variablen und des Operandenstapels aus den Klassendaten, erstellt eine entsprechende Größe und einen geeigneten Rahmen und schiebt sie dann auf den Java-Stack.
1. Lokale Variablen
Lokale Variablen werden als Array ab 0 im Java-Stack-Frame organisiert. Die Anweisung erhält den entsprechenden Wert aus dem lokalen Variablenbereich, indem sie ihren Index bereitstellt. Int, Float, Reference, ReturnValue belegen ein Wort, Byte, Short und Char werden in int konvertiert und dann gespeichert, Long und Doublel belegen zwei Wörter. Der Befehl
erhält den Wert long oder double, indem er den ersten von zwei Wortindizes bereitstellt. Wenn beispielsweise ein Long-Wert am Index 3 oder 4 gespeichert ist, kann der Befehl 3 verwenden, um den Long-Wert zu erhalten.
Der lokale Variablenbereich enthält Methodenparameter und lokale Variablen. Der Compiler platziert Methodenparameter am Anfang des Arrays in der Reihenfolge, in der sie deklariert werden. Der Compiler kann jedoch lokale Variablen beliebig im lokalen Variablenarray anordnen, und sogar zwei lokale Variablen können eine gemeinsame Adresse haben. Beispielsweise können sich zwei lokale Variablen in zwei nicht überlappenden Bereichen befinden, wie z. B. die Schleifenvariablen i, j.
Der Implementierer der virtuellen Maschine kann eine beliebige Struktur verwenden, um die Daten im lokalen Variablenbereich zu beschreiben. Die Spezifikation der virtuellen Maschine definiert nicht, wie Long und Doublel gespeichert werden.
2. Operandenstapel (Operandenstapel)
Wie lokale Variablen ist auch der Operandenstapel in einem Array in Worteinheiten organisiert. Der Zugriff erfolgt jedoch nicht wie bei lokalen Variablen über einen Index, sondern über Push- und Pop-Werte. Wenn eine Anweisung einen Wert auf den Stapel schiebt, kann die nächste Anweisung diesen Wert abrufen und verwenden.
Im Gegensatz zum Programmzähler können Anweisungen nicht direkt auf den Operandenstapel zugreifen. Die Java Virtual Machine ist stapelbasiert und nicht registerbasiert, da ihre Anweisungen Operanden vom Stapel und nicht aus demselben Register beziehen. Natürlich können Anweisungen auch Operanden von anderen Stellen erhalten, beispielsweise vom Opcode nach der Anweisung oder vom Konstantenpool. Die Anweisungen der Java Virtual Machine beziehen die benötigten Operanden jedoch hauptsächlich aus dem Operandenstapel.
Die Java Virtual Machine behandelt den Operandenstapel als Arbeitsbereich. Viele Anweisungen entnehmen den Wert zunächst dem Operandenstapel und verschieben das Ergebnis nach der Verarbeitung zurück in den Operandenstapel. Der Ausführungsprozess einer Add-Anweisung ist in der folgenden Abbildung dargestellt: Führen Sie zuerst die Anweisungen iload_0 und iload_1 aus, um die beiden hinzuzufügenden Zahlen aus dem lokalen Methodenbereich zu übernehmen und sie auf den Operandenstapel zu übertragen. Führen Sie dann die Anweisung iadd aus Jetzt pop Geben Sie zwei Werte aus, fügen Sie sie hinzu und verschieben Sie das Ergebnis in den Operandenstapel. Führen Sie schließlich die Anweisung istore_2 aus, geben Sie das Ergebnis aus und weisen Sie es dem lokalen Methodenbereich zu.
3. Rahmendaten
Zusätzlich zur Verarbeitung lokaler Variablen und Operandenstapel enthalten Java-Stack-Rahmen auch Daten, die zur Unterstützung konstanter Pools, Methodenrückgabewerte und Ausnahmeverteilung erforderlich sind, und werden in Rahmendaten gespeichert.
Wenn die virtuelle Maschine auf eine Anweisung stößt, die einen Verweis auf den Konstantenpool verwendet, greift sie über den Zeiger auf den Konstantenbereich in den Rahmendaten auf die erforderlichen Informationen zu. Wie bereits erwähnt, handelt es sich bei Referenzen im konstanten Bereich um symbolische Referenzen am Anfang. Auch wenn die virtuelle Maschine diese Referenzen überprüft, handelt es sich um Zeichenreferenzen. Daher muss die virtuelle Maschine diese Referenz zu diesem Zeitpunkt konvertieren.
Wenn eine Methode normal zurückkehrt, muss die virtuelle Maschine den Stapelrahmen der Methode rekonstruieren, die diese Methode aufgerufen hat. Wenn die ausgeführte Methode einen Rückgabewert hat, muss die virtuelle Maschine diesen Wert in den Operandenstapel der aufrufenden Methode verschieben.
Die Rahmendaten enthalten auch einen Verweis auf die Ausnahmetabelle, die von der virtuellen Maschine zur Behandlung von Ausnahmen verwendet wird. Die Ausnahmetabelle definiert einen Abschnitt des Bytecodes, der durch eine Catch-Anweisung geschützt ist. Jede Person in der Ausnahmetabelle enthält außerdem den Bereich der zu schützenden Bytecodes und den Speicherort des Bytecodes, der ausgeführt werden muss, wenn die Ausnahme abgefangen wird. Wenn eine Methode eine Ausnahme auslöst, verwendet die Java Virtual Machine die Ausnahmetabelle, um zu bestimmen, wie die Ausnahme behandelt werden soll. Wenn die virtuelle Maschine einen passenden Catch findet, übergibt sie die Kontrolle an die Catch-Anweisung. Wenn kein passender Catch gefunden wird, kehrt die Methode abnormal zurück und setzt den Prozess dann in der aufrufenden Methode fort.
Zusätzlich zu den oben genannten drei Verwendungszwecken können Frame-Daten auch einige von der Implementierung abhängige Daten enthalten, z. B. Debugging-Informationen.
12. Lokaler Methodenstapel
Der lokale Methodenbereich hängt von verschiedenen Implementierungen der virtuellen Maschine ab. Der Implementierer der virtuellen Maschine kann entscheiden, welchen Mechanismus er zum Ausführen nativer Methoden verwenden möchte.
Jede native Methodenschnittstelle verwendet irgendeine Form eines nativen Methodenstapels.
13. Ausführungs-Engine
Der Kern einer Java Virtual Machine-Implementierung ist die Ausführungs-Engine. In der Java Virtual Machine Specification wird die Ausführungs-Engine als eine Folge von Anweisungen beschrieben. Für jede Direktive beschreibt die Spezifikation, was sie tun soll, aber nicht, wie es zu tun ist.
1. Befehlssatz
In der Java Virtual Machine ist der Bytecode-Stream einer Methode eine Folge von Anweisungen. Jede Anweisung besteht aus einem Byte-Operationscode (Opcode) und möglichen Operanden (Operanden). Der Opcode gibt an, was zu tun ist, und die Operanden stellen einige zusätzliche Informationen bereit, die möglicherweise zum Ausführen des Opcodes erforderlich sind. Eine abstrakte Ausführungs-Engine führt jeweils eine Anweisung aus. Dieser Prozess findet in jedem Ausführungsthread statt.
Manchmal stößt die Ausführungs-Engine auf eine Anweisung, die den Aufruf einer lokalen Methode erfordert. In diesem Fall versucht die Ausführungs-Engine, die lokale Methode aufzurufen, aber wenn die lokale Methode zurückkehrt, führt die Ausführungs-Engine den nächsten Schritt weiter aus Der Bytecode-Stream. Native Methoden können auch als Erweiterung des Befehlssatzes in der Java Virtual Machine angesehen werden.
Die Entscheidung, welche Anweisung als nächstes ausgeführt werden soll, ist ebenfalls Teil der Arbeit der Ausführungs-Engine. Die Ausführungsmaschine verfügt über drei Methoden, um die nächste Anweisung abzurufen. Die meisten Anweisungen führen die darauffolgenden Anweisungen aus; einige Anweisungen wie „goto“ und „return“ bestimmen ihre nächste Anweisung, wenn eine Anweisung eine Ausnahme auslöst ausgeführt werden.
Plattformunabhängigkeit, Netzwerkmobilität und Sicherheit beeinflussen das Design des Java Virtual Machine-Befehlssatzes. Die Plattformunabhängigkeit ist einer der Haupteinflussfaktoren beim Befehlssatzdesign. Durch die stapelbasierte Struktur kann die Java Virtual Machine auf mehr Plattformen implementiert werden. Kleinere Opcodes und eine kompakte Struktur ermöglichen es Bytecode, die Netzwerkbandbreite effizienter zu nutzen. Durch die einmalige Überprüfung des Bytecodes wird der Bytecode sicherer, ohne die Leistung zu stark zu beeinträchtigen.
2. Ausführungstechnologie
Viele Ausführungstechnologien können bei der Implementierung der Java Virtual Machine verwendet werden: interpretierte Ausführung, Just-in-Time-Kompilierung, Hot-Spot-Kompilierung, native Ausführung in Silizium.
3. Threads
Die Java Virtual Machine-Spezifikation definiert ein Threading-Modell für die Implementierung auf mehr Plattformen. Ein Ziel des Java-Threading-Modells besteht darin, native Threads zu nutzen. Durch die Verwendung lokaler Threads können die Threads in einem Java-Programm gleichzeitig auf einem Multiprozessorrechner ausgeführt werden.
Einer der Kosten des Java-Threading-Modells ist die Thread-Priorität. Ein Java-Thread kann mit einer Prioritätsstufe von 1-10 ausgeführt werden. 1 ist der niedrigste Wert und 10 der höchste Wert. Wenn der Designer native Threads verwendet hätte, hätte er diese 10 Prioritäten möglicherweise lokalen Prioritäten zugeordnet. Die Java Virtual Machine-Spezifikation definiert nur, dass Threads mit höherer Priorität etwas CPU-Zeit erhalten können, und Threads mit niedrigerer Priorität können auch etwas CPU-Zeit erhalten, wenn alle Threads mit hoher Priorität blockiert sind. Es gibt jedoch keine Garantie: Niedrige Priorität Der Thread kann nicht erhalten eine bestimmte Menge an CPU-Zeit, wenn der Thread mit hoher Priorität nicht blockiert ist. Wenn Sie daher zwischen verschiedenen Threads zusammenarbeiten müssen, müssen Sie „synchronizatoin“ verwenden.
Synchronisation besteht aus zwei Teilen: Objektsperre und Thread-Wartezeit und -Benachrichtigung. Objektsperren helfen Threads, frei von Störungen durch andere Threads zu bleiben. Das Warten und Aktivieren von Threads ermöglicht die Zusammenarbeit verschiedener Threads.
In der Spezifikation der Java Virtual Machine werden Java-Threads als Variablen, Hauptspeicher und Arbeitsspeicher beschrieben. Jede Instanz der Java Virtual Machine verfügt über einen Hauptspeicher, der alle Programmvariablen enthält: Objekte, Arrays und Klassenvariablen. Jeder Thread verfügt über einen eigenen Arbeitsspeicher und speichert Kopien der Variablen, die er möglicherweise verwendet. Regeln:
1), den Wert der Variablen vom Hauptspeicher in den Arbeitsspeicher kopieren
2), den Wert im Arbeitsspeicher in den Hauptspeicher schreiben
Wenn eine Variable nicht synchronisiert ist, aktualisiert der Thread möglicherweise den Hauptspeicher in beliebiger Reihenfolge variabel. Um die korrekte Ausführung von Multithread-Programmen sicherzustellen, muss ein Synchronisationsmechanismus verwendet werden.
14. Native Methodenschnittstelle
Die Implementierung der Java Virtual Machine muss nicht die native Methodenschnittstelle implementieren. Einige Implementierungen unterstützen möglicherweise überhaupt keine nativen Methodenschnittstellen. Die native Methodenschnittstelle von Sun ist JNI (Java Native Interface).
15. Die echte Maschine
16. Mathematische Methode: Simulation (Ewige Mathematik: Eine Simulation)
Das obige ist der detaillierte Inhalt vonEinführung in die Wissenspunkte der Java Virtual Machine (JVM). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!