Grundkenntnisse in JVM (1)

1. Gesamtarchitektur und Komponenten

        1.Klassenlader

        Der Klassenlader (Klassenlader) ist dafür verantwortlich, .class-Dateien in die JVM zu laden und entsprechende Java-Klassenobjekte (Klassenobjekte) zu generieren. In Java gibt es drei Klassenlader:

  • Bootstram ClassLoader: Lädt die in C++ implementierte Kernklassenbibliothek und ist Teil der JVM;
  • Extension ClassLoader: Lädt Java-Erweiterungsklassenbibliotheken (z. B. javax.*-Pakete), die in Java implementiert sind, und ist Teil von sun.misc.Launcher;
  • App ClassLoader: Das Laden von Anwendungsklassen, implementiert in Java, ist eine Unterklasse von ClassLoader.

        .2.Ausführungs-Engine

        Die Execution Engine ist dafür verantwortlich, den Bytecode zu interpretieren, ihn in Maschinenanweisungen umzuwandeln und das Programm auszuführen. Die Ausführungs-Engine der JVM umfasst zwei Modi:

  • Interpretationsmodus: Bytecode Zeile für Zeile interpretieren und entsprechende Maschinenanweisungen ausführen. Der Vorteil dieses Modus besteht darin, dass er schnell gestartet werden kann, die Ausführungsgeschwindigkeit jedoch langsamer ist.
  • Compilermodus: Kompilieren Sie Bytecode in lokale Maschinenanweisungen und führen Sie den kompilierten Code aus. Der Vorteil dieses Modus besteht darin, dass die Ausführungsgeschwindigkeit hoch, die Startgeschwindigkeit jedoch langsam ist.

        Die JVM verwendet einen gemischten Modus (Mixed Mode), der je nach Bedarf während der Ausführung des Programms dynamisch zwischen dem Interpretermodus und dem Compilermodus wählt.

        3.Laufzeitdatenbereich

Der Laufzeitdatenbereich (Laufzeitdatenbereich) ist der Datenspeicherbereich in der JVM, einschließlich der folgenden Komponenten:

  • Methodenbereich: Klasseninformationen, Konstanten, statische Variablen und andere Daten speichern.
  • Heap (Heap): Speichert Java-Objekte und -Arrays.
  • Stapel (Stack): Speichert Informationen wie lokale Variablen, Operandenstapel und Methodenexits von Java-Methoden.
  • PC-Register (Programmzähler): Speichert die Adresse der ausgeführten Java-Methode.
  • Native Method Stack: Bietet Unterstützung für Java zum Aufrufen nativer Methoden.

        4.Native Methodenschnittstelle

        Native Method Interface (lokale Methodenschnittstelle) ermöglicht Java-Code den Aufruf nativer Methoden, die in C oder C++ geschrieben sind. Native Methoden werden über JNI (Java Native Interface) implementiert. JVM konvertiert Java-Parameter in C/C++-Parameter, ruft native Methoden auf und gibt Ergebnisse zurück.

        Die Gesamtarchitektur und die Komponenten der JVM stellen die Grundlage für die Ausführung von Java-Programmen dar. Für Java-Programmierer kann das Verständnis der Prinzipien und internen Mechanismen der JVM dabei helfen, effizientere und stabilere Java-Programme zu schreiben.

2. Interaktion zwischen Komponenten

        2.1 Klassenlader und Methodenbereich

Wenn eine Java-Klasse zum ersten Mal geladen wird, liest Class Loader die .class-Datei der Klasse in den Speicher und erstellt das entsprechende Klassenobjekt im Methodenbereich, einschließlich der Mitgliedsvariablen und Methoden der Klasse. Während der Ausführung der JVM werden alle Klasseninformationen im Methodenbereich gespeichert, einschließlich Klassenbytecode, Laufzeitkonstantenpool, Feld- und Methodeninformationen usw.

        2.2 Heap und Stack

        Wenn in einem Java-Programm Objekte erstellt werden, werden deren Instanzdaten im Heap und Objektreferenzen im Stack gespeichert. Jede Java-Methode erstellt einen Stapelrahmen (Stack Frame), in dem die lokalen Variablen der Methode, der Operandenstapel, der Methodenausgang und andere Informationen gespeichert werden. Der Stapelrahmen wird auch im Stapel gespeichert.

        Wenn eine Java-Methode aufgerufen wird, erstellt die JVM einen neuen Stapelrahmen im Stapel und verschiebt den Stapelrahmen an die Spitze des Stapels. Wenn die Methodenausführung abgeschlossen ist, öffnet die JVM den Stapelrahmen und beansprucht seinen Speicherplatz zurück.

        2.3 Execution Engine und Methodenbereich

        Die Execution Engine ist dafür verantwortlich, Java-Bytecode auszuführen und ihn in Maschinenanweisungen umzuwandeln, die der Computer ausführen kann. Vor der Ausführung des Bytecodes muss die Ausführungs-Engine Informationen wie Bytecodes und Konstanten im Methodenbereich finden und diese in den Laufzeitkonstantenpool laden.

        2.4 Schnittstelle für native Methoden und Stack für native Methoden

        Wenn ein Java-Programm eine in C/C++ geschriebene native Methode aufrufen muss, konvertiert die JVM Java-Parameter in C/C++-Parameter und ruft die native Methode auf. Das Ergebnis der nativen Methode wird an das Java-Programm zurückgegeben und das C/C++-Ergebnis muss in das Java-Ergebnis konvertiert werden.

        Native Method Interface bietet einen Mechanismus zum Aufrufen nativer, in C/C++ geschriebener Methoden in Java-Programmen, wodurch Java-Programme mit dem zugrunde liegenden System interagieren können.

3. Das Funktionsprinzip des Klassenladers und der Prozess des Klassenladens

        Der Klassenlader (ClassLoader) ist ein wichtiger Teil der JVM, der für das Laden von Java-Klassen von externen Speichern wie Festplatten oder Netzwerken in den Speicher der JVM verantwortlich ist. Der Klassenlader übernimmt das „Eltern-Delegierungsmodell“, das heißt, wenn eine Klasse geladen werden muss, vertraut er zunächst seinem übergeordneten Klassenlader die Suche nach der Klasse an, bis er schließlich an den Start-Klassenlader delegiert. Wenn keiner der übergeordneten Klassenlader die Klasse finden kann, wird sie vom Klassenlader selbst geladen.

Der Prozess des Klassenladens umfasst normalerweise die folgenden drei Phasen:

  1. Laden: Suchen und laden Sie die Binärdaten der Klasse. Der Klassenlader findet zunächst die entsprechende .class-Datei über den vollständig qualifizierten Namen der Klasse, liest dann die Binärdaten in den Speicher und erstellt das entsprechende Klassenobjekt im Speicher. Es ist zu beachten, dass dieselbe Klasse nur einmal in die JVM geladen wird.

  2. Verknüpfen: Zusammenführen der Binärdaten der Klasse in die Laufzeitumgebung der JVM. Die Verbindungsphase besteht aus drei Schritten:

  • Überprüfung: Stellen Sie sicher, dass die Binärdaten der Klasse der JVM-Spezifikation entsprechen und keine Sicherheitslücken aufweisen.
  • Vorbereitung: Speicher für statische Variablen der Klasse zuweisen und Standardwerte festlegen.
  • Lösung: Ersetzen Sie symbolische Referenzen durch direkte Referenzen, dh konvertieren Sie Referenzen wie Klassen, Felder, Methoden usw. in Speicheradressen.
  1. Initialisierung (Initialisierung): Weisen Sie den statischen Variablen der Klasse Anfangswerte zu und führen Sie den statischen Codeblock der Klasse aus. In der JVM ist die Initialisierung einer Klasse eine threadsichere Operation, die sicherstellt, dass die Klasse nur einmal initialisiert wird. Wenn die Klasse eine übergeordnete Klasse hat, wird die übergeordnete Klasse zuerst initialisiert.

        Es ist zu beachten, dass die JVM eine Klasse nur dann lädt und initialisiert, wenn sie eine Klasse verwenden muss. Dieser Mechanismus wird als „verzögertes Laden“ bezeichnet. Gleichzeitig unterstützt JVM auch einen dynamischen Klassenlademechanismus, der während der Programmausführung neue Klassen über den Java-Reflexionsmechanismus laden und zur JVM hinzufügen kann.

4. Grundlegende Syntax und Verwendung des Bytecode-Befehlssatzes

        Nachdem der Java-Code vom Compiler kompiliert wurde, wird er in Bytecode (Bytecode) konvertiert, bei dem es sich um einen plattformübergreifenden Zwischencode handelt. Der Bytecode-Befehlssatz ist ein Codeformat, das die Java Virtual Machine (JVM) erkennen und ausführen kann. Der Bytecode-Befehlssatz ist prägnant und kompakt und hat nichts mit der zugrunde liegenden Hardwarearchitektur zu tun, sodass er auf verschiedenen Plattformen ausgeführt werden kann.

        Der Bytecode-Befehlssatz besteht aus Befehlen eines einzelnen Bytes. Jeder Befehl verfügt über einen Operationscode (Opcode) und einen oder mehrere Operanden. Die Java Virtual Machine schließt die Ausführung von Java-Programmen ab, indem sie eine Reihe von Bytecode-Anweisungen ausführt.

Im Folgenden sind einige grundlegende Syntax und Verwendung des Bytecode-Befehlssatzes aufgeführt:

        4.1 Anweisungen zum Laden und Speichern

  • Laden Sie Werte aus der lokalen Variablentabelle in den Operandenstapel: iload, dload, aload usw.
  • Speichern Sie Werte vom Operandenstapel in der lokalen Variablentabelle: istore, dstore, astore usw.

        4.2 Bedienungsanleitung

  • Binäre Betriebsanweisungen: iadd, dadd, isub, dsub, imul, dmul, idiv, ddiv, irem, drem usw.
  • Anweisungen zur Bitbedienung: ishl, ishr, iushr, iand, ior, ixor usw.

        4.3 Anweisungen zur Typkonvertierung

  • Konvertieren Sie ganzzahlige Werte in andere Typen: i2d, i2l, i2f usw.
  • Konvertieren Sie Gleitkommawerte in andere Typen: d2i, d2l, d2f usw.
  • Konvertieren Sie lange Ganzzahlwerte in andere Typen: l2i, l2d, l2f usw.

        4.4 Steuerbefehle

  • Bedingte Sprunganweisungen: ifeq, ifne, iflt, ifgt, ifle, ifge usw.
  • Unbedingte Sprunganweisungen: goto, goto_w usw.
  • Rückgabebefehle: ireturn, dreturn, areturn usw.

        4.5 Anweisungen zur Objektbedienung

  • Erstellen Sie neue Objektanweisungen: new, newarray, anewarray usw.
  • Anweisungen zur Feldbedienung: getfield, putfield, getstatic, putstatic usw.
  • Anweisungen zum Methodenaufruf: invokevirtual, invokespecial, invokestatic, invokeinterface usw.

        Im Allgemeinen stellt der Bytecode-Befehlssatz die grundlegende Syntax und Verwendung der Java Virtual Machine zum Ausführen von Java-Programmen bereit und ist auch eine der wichtigen Garantien für den plattformübergreifenden Betrieb von Java-Programmen.

5. JIT-Compiler und AOT-Compiler

        Sowohl JIT-Compiler als auch AOT-Compiler sind Tools, die Code in Maschinencode umwandeln, es gibt jedoch große Unterschiede in ihrer Funktionsweise und ihren Vor- und Nachteilen.

        Der JIT-Compiler (Just-In-Time Compiler) kompiliert Bytecodes in lokale Maschinencodes zur Ausführung in Echtzeit während der Programmausführung. Der JIT-Compiler kann Hotcodes entsprechend den tatsächlichen Betriebsbedingungen des Programms optimieren, um die Ausführungseffizienz des Programms zu verbessern. Zu den Vorteilen eines JIT-Compilers gehören:

  • Durch die Just-in-Time-Kompilierung wird die lange Startzeit vermieden, die durch die Vorkompilierung verursacht wird.
  • Die dynamische Kompilierung kann entsprechend dem tatsächlichen Betrieb des Programms optimiert werden, um die Ausführungseffizienz des Programms zu verbessern.
  • In enger Verbindung mit der Java Virtual Machine verbessert es die Portabilität und Kompatibilität des Programms.

Zu den Nachteilen von JIT-Compilern gehören:

  • Die Kompilierungszeit ist länger, was sich auf die Antwortzeit des Programms auswirken kann.
  • Bei einigen Codes, die nur einmal ausgeführt werden, führt der JIT-Compiler keine Optimierung durch, wodurch einige Leistungsressourcen verschwendet werden.
  • Der JIT-Compiler benötigt mehr Speicherplatz.

Der AOT-Compiler (Ahead-Of-Time Compiler) kompiliert Java-Bytecode in lokalen Maschinencode, bevor das Programm ausgeführt wird, um eine ausführbare Datei zu generieren. Der AOT-Compiler kann das gesamte Programm durch statische Kompilierung optimieren, um die Ausführungseffizienz des Programms zu verbessern. Zu den Vorteilen eines AOT-Compilers gehören:

  • Kürzere Kompilierungszeiten und kürzere Startzeiten;
  • Eine globale Optimierung kann durchgeführt werden, um das gesamte Programm zu optimieren und die Ausführungseffizienz des Programms zu verbessern.
  • Programme können ohne eine Java Virtual Machine ausgeführt werden.

Zu den Nachteilen von AOT-Compilern gehören:

  • Mangels dynamischer Optimierung ist es unmöglich, den tatsächlichen Betrieb des Programms zu optimieren.
  • Kann zu Portabilitäts- und Kompatibilitätsproblemen führen;
  • Es nimmt viel Speicherplatz in Anspruch und lässt sich nur schwer auf Umgebungen mit eingeschränkten Ressourcen anwenden.

        Zusammenfassend lässt sich sagen, dass JIT-Compiler und AOT-Compiler unterschiedliche Implementierungsmethoden sowie Vor- und Nachteile haben und jeweils für unterschiedliche Szenarien geeignet sind. In der Java Virtual Machine ist der JIT-Compiler der Mainstream-Compiler, der dynamische Optimierung und bessere Portabilität bieten kann, während der AOT-Compiler für einige spezifische Szenarien besser geeignet ist, z. B. eingebettete Systeme oder mobile Anwendungen.

 Willkommen bei: http://mumuxi.chat/

Supongo que te gusta

Origin blog.csdn.net/zz18532164242/article/details/130543436
Recomendado
Clasificación