Erste Schritte mit JVM – Java Virtual Machine Register-Befehlssatz und Stack-Befehlssatz

Erste Schritte mit JVM – Java Virtual Machine Register-Befehlssatz und Stack-Befehlssatz

  • Jeder Vorgang in der virtuellen HotSpot-Maschine erfordert Push- und Popping-Schritte.

  • Aufgrund des plattformübergreifenden Designs werden Java-Anweisungen auf Basis des Stacks entworfen. Verschiedene Plattformen verfügen über unterschiedliche CPU-Architekturen und können daher nicht registerbasiert konzipiert werden. Die Vorteile bestehen darin, dass es plattformübergreifend ist, der Befehlssatz klein ist und der Compiler einfach zu implementieren ist. Der Nachteil besteht darin, dass die Leistung verringert wird und mehr Anweisungen erforderlich sind, um dieselbe Funktion zu erreichen.

Verweise

  • Java Virtual Machine-Spezifikation (JavaSE8)
  • Vertiefte Kenntnisse der Java Virtual Machine

Zwei Hauptmerkmale des Befehlssatzes von JVM

Funktionen basierend auf der Stack-Architektur

Einfacheres Design und Implementierung, geeignet für ressourcenbeschränkte Systeme (die virtuelle HotSpot-Maschine basiert darauf):

  • Vermeiden Sie Probleme bei der Registerzuordnung: Verwenden Sie zur Zuweisung Nulladressenanweisungen.
  • Die meisten Anweisungen im Befehlsstrom sind Anweisungen mit Nulladresse, und ihre Ausführung hängt vom Operationsstapel ab.
  • Der Befehlssatz ist kleiner und für Compiler einfacher zu implementieren.
  • Es ist keine Hardwareunterstützung erforderlich, die Portabilität ist besser und die plattformübergreifende Implementierung ist besser

Merkmale der registerbasierten Architektur

Typische Anwendungen sind x86-Binärbefehlssätze: beispielsweise herkömmliche PCs und die virtuelle Davlik-Maschine von Android.

  • Die Befehlssatzarchitektur ist vollständig auf Hardware angewiesen und weist eine schlechte Portabilität auf.
  • Hervorragende Leistung und effizientere Ausführung, wodurch weniger Anweisungen zum Abschließen eines Vorgangs erforderlich sind.
  • In den meisten Fällen wird der Befehlssatz, der auf der Registerarchitektur basiert, tendenziell von Befehlen mit einer Adresse, Befehlen mit zwei Adressen und Befehlen mit drei Adressen dominiert, während der Befehlssatz, der auf der stapelbasierten Architektur basiert, von Befehlen mit Nulladresse dominiert wird Anweisungen.

Holen Sie sich den Stack-Befehlssatz-Befehl javap

Hinweis: Wenn Sie JDK17 verwenden, können Sie den Konsolenbefehl möglicherweise nicht finden (da die Standardinstallation diesen Befehl nicht konfiguriert). Gehen Sie einfach in das Installationsverzeichnis von JDK17 und fügen Sie bin zur Umgebungsvariablen hinzu.

Kompilieren Sie zunächst ein einfaches 1+2Programm:

public class StackOneTest {
    
    
	public static void main(String[] args) {
    
    
		int a = 1;
		int b = 2;
		int c = a+b;
	}
}

Verwenden Sie dann javap -c class文件全名, wir erhalten den folgenden Inhalt in der Konsole:

D:\CodeProjects\Eclipse\StackTest\bin\testjava>javap -c StackOneTest.class
Compiled from "StackOneTest.java"
public class testjava.StackOneTest {
    
    
  public testjava.StackOneTest();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_1
       1: istore_1
       2: iconst_2
       3: istore_2
       4: iload_1
       5: iload_2
       6: iadd
       7: istore_3
       8: return
}

Da es keine experimentelle Umgebung für registerbasierte Befehlssätze gibt, können Sie die Methode selbst überprüfen.

Semantik des Stack-Befehlssatz-Bytecodes

Überblick über das Design des Bytecode-Befehlssatzes

Im Befehlssatz der Java Virtual Machine enthalten die meisten Anweisungen Informationen über die Datentypen, die sie bedienen. Beispielsweise iloadwird die Anweisung verwendet, um Daten des Typs aus der lokalen Variablentabelle intin den Operandenstapel zu laden, während die Anweisung Daten des Typs adlädt . floatDie Operationen dieser beiden Anweisungen können durch denselben Code implementiert werden, sie müssen jedoch über unabhängige Opcodes verfügen. Bei den meisten Bytecode-Anweisungen, die sich auf Datentypen beziehen, verfügen die Opcode-Mnemoniken über Sonderzeichen, die angeben, welchen Datentyp die Anweisung bedient: istellt intdie Datenoperation vom Typ dar, lrepräsentiert long, repräsentiert, srepräsentiert short, brepräsentiert byte, crepräsentiert char, frepräsentiert float, drepräsentiert double, arepräsentiert reference. Es gibt auch einige Anweisungen, deren Mnemonik den Datentyp nicht explizit mit Buchstaben angibt, z. B. arraylengthdie Anweisung, die keine Sonderzeichen zur Darstellung des Datentyps enthält, der Operand jedoch immer nur ein Objekt vom Typ Array sein kann. Es gibt andere Anweisungen, z. B. unbedingte Sprunganweisungen, gotodie unabhängig vom Datentyp sind.

Da die Opcode-Länge der Java Virtual Machine nur ein Byte beträgt, üben Opcodes, die Datentypen enthalten, großen Druck auf das Design des Befehlssatzes aus. Wenn jede datentypbezogene Anweisung alle Laufzeitdatentypen der Java Virtual Machine unterstützt, kann es sein, dass sie den Zahlenbereich überschreitet, der durch ein Byte dargestellt werden kann. Daher stellt der Befehlssatz der Java Virtual Machine nur begrenzte typbezogene Anweisungen für bestimmte Operationen bereit. Mit anderen Worten, der Befehlssatz wird bewusst so konzipiert, dass er nicht vollständig unabhängig ist (nicht orthogonal, d. h. nicht jeder Datentyp und jeder). Für jeden Vorgang gibt es entsprechende Anweisungen. Es gibt separate Anweisungen, mit denen bei Bedarf nicht unterstützte Typen in unterstützte Typen konvertiert werden können.

Tabelle 2-2 listet die von der Java Virtual Machine unterstützten Bytecode-Befehlssätze auf. Verwenden Sie die durch die Datentypspalte dargestellten Sonderzeichen, um opcodedie Anweisungsvorlage der Spalte zu ersetzen Tund eine bestimmte Bytecode-Anweisung zu erhalten. Wenn die durch die Spalten „Anweisungsvorlage“ und „Datentyp“ in der Tabelle bestimmte Zelle leer ist, bedeutet dies, dass die virtuelle Maschine die Ausführung dieser Operation für diesen Datentyp nicht unterstützt. loadAnweisungen haben beispielsweise den intOperationstyp iload, es gibt jedoch keine byteähnlichen Anweisungen vom Operationstyp. Bitte beachten Sie, dass, wie aus Tabelle 2-2 hervorgeht, die meisten Anweisungen die Integer-Typen byte, charund nicht unterstützen shortund keine der Anweisungen booleanden Typ überhaupt unterstützt. Der Compiler erweitert die „ byteand“ -Typdaten zur Kompilierungszeit oder zur Laufzeit mit Vorzeichen auf die entsprechenden Typdaten und führt eine Nullerweiterung der „ and“ -Typdaten auf die entsprechenden Typdaten durch. Ebenso werden bei der Verarbeitung von Arrays der Typen , und diese ebenfalls konvertiert , um die Bytecode-Anweisungen des entsprechenden Typs für die Verarbeitung zu verwenden. Daher können die meisten Operationen mit Operanden der tatsächlichen Typen , und mithilfe von Anweisungen mit Operanden des Rechentyps (Rechentyp) ausgeführt werden .shortintbooleancharintbooleanbyteshortcharintbooleanbytecharshortint

aus: Java Virtual Machine Specification (JavaSE8)

Zeitplan:

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Tatsächliche Typ- und Beziehungszuordnungstabelle:

Fügen Sie hier eine Bildbeschreibung ein

Befehlssatz laden und speichern

Anweisungen zum Laden lokaler Variablen auf den Operandenstapel:

Anweisung beschreiben
iload Laden Sie den Typ int in den Operandenstapel
iload <n> Lädt den int-Typ am angegebenen Index in den Operandenstapel
lload_<n> Lädt den Long-Typ am angegebenen Index in den Operandenstapel
fload Laden Sie den Float-Typ in den Operandenstapel
fload <n> Lädt den Float-Typ am angegebenen Index in den Operandenstapel
dload Laden Sie den Double-Typ in den Operandenstapel
dload <n> Lädt den Double-Typ am angegebenen Index in den Operandenstapel
aload Referenztypen auf den Operandenstapel laden
aload <n> Lädt den Referenztyp am angegebenen Index in den Operandenstapel

Anweisungen zum Speichern des Operandenstapels in der lokalen Variablentabelle:

Anweisung beschreiben
istore Speichern Sie den Typ int in der lokalen Variablentabelle
istore <n> Speichern Sie den int-Typ in einer lokalen Variablen am angegebenen Index
lstore <n> Speichern Sie den Long-Typ in einer lokalen Variablen am angegebenen Index
fstore Speichern Sie den Float-Typ in der lokalen Variablentabelle
fstore <n> Speichern Sie den Float-Typ in einer lokalen Variablen am angegebenen Index
dstore Speichern Sie den Doppeltyp in der lokalen Variablentabelle
dstore <n> Speichert einen Double-Typ in einer lokalen Variablen am angegebenen Index
astore Speichern Sie den Referenztyp in der lokalen Variablentabelle
astore_<n> Speichert einen Referenztyp in einer lokalen Variablen am angegebenen Index

Anweisungen zum Laden von Konstanten auf den Operandenstapel:

Anweisung beschreiben
bipush Laden Sie die vorzeichenbehaftete Bytekonstante (-128~127) in den Operandenstapel
sipush Laden Sie die vorzeichenbehaftete Kurzkonstante (-32768~32767) in den Operandenstapel
ldc Laden Sie eine Konstante vom Typ int, float oder String in den Operandenstapel
ldc_w Ähnlich wie ldc, aber für größere konstante Poolindizes
ldc2_w Laden Sie eine Konstante vom Typ long oder double auf den Operandenstapel
aconst_null Laden Sie null auf den Operandenstapel
iconst_m1 Laden Sie die Ganzzahl -1 auf den Operandenstapel
iconst <i> Laden Sie ganzzahlige Konstanten auf den Operandenstapel
lconst <1> Laden Sie die Long-Integer-Konstante 1 auf den Operandenstapel
fconst <f> Laden Sie Gleitkommakonstanten in den Operandenstapel
dconst <d> Laden Sie eine Gleitkommakonstante mit doppelter Genauigkeit auf den Operandenstapel

Anweisungen zum Erweitern der lokalen Variablentabelle, um auf Indizes oder unmittelbare Werte zuzugreifen:

Anweisung beschreiben
wide Erweitern Sie die Breite des lokalen Variablenindex, der von der nächsten Anweisung verwendet wird

Arithmetischer Befehlssatz

Rechenanweisungen:

Operationstyp Anweisung
Zusatz iaddladdfadddadd
Subtraktion isubIsubfsubdsub
Multiplikation imulmulfmuldmul
Aufteilung idivldivfdivddiv
Fragen Sie nach Überschüssen iremIremfremdrem

Anweisungen zur logischen Bitoperation:

Operationstyp Anweisung
Bitweises ODER iorlor
Bitweises UND iandland
Bitweises XOR ixorlxor

Weitere Bedienungsanleitungen:

Operationstyp Anweisung
negativen Wert finden inegInegfnegdneg
Schicht ishlishriushrIshlIshrlushr
Automatische Inkrementierung lokaler Variablen iinc
Vergleichen dcmpgdcmplfcmpgfcmplIcmp

Bytecode-Analyse des Stapelbefehlssatzes

Arithmetischer Bytecode in der Hauptmethode

D:\CodeProjects\Eclipse\StackTest\bin\testjava>javap -c StackOneTest.class
Compiled from "StackOneTest.java"
public class testjava.StackOneTest {
    
    
  public testjava.StackOneTest();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_1
       1: istore_1
       2: iconst_2
       3: istore_2
       4: iload_1
       5: iload_2
       6: iadd
       7: istore_3
       8: return
}

Aus dem obigen Bytecode ist leicht zu erkennen, dass JVMzuerst das Objekt erstellt StackOneTestund dann die Objektvariable in den Operandenstapel (Standardkonstruktor) geladen wird. Konzentrieren wir uns auf den mainBefehlssatz:

0: iconst_1
1: istore_1
2: iconst_2
3: istore_2

Zunächst 1werden die beiden Konstanten 2in den Operandenstapel geladen und gespeichert. Als nächstes:

4: iload_1
5: iload_2
6: iadd
7: istore_3

Zwei Variablen werden auf den Operandenstapel geladen a, bdann mithilfe von arithmetischen Anweisungen hinzugefügt und schließlich wird das Ergebnis gespeichert (die dritte Variable wird hier nicht verwendet, das Laden der Variablen wurde möglicherweise vom Compiler optimiert). Wir modifizieren es geringfügig:

Fügen Sie hier eine Bildbeschreibung ein

Es kann festgestellt werden, dass nach Verwendung der dritten Variablen JVMdas korrekte Laden der Variablen durchgeführt wurde

Bytecode-Analyse mit Methoden

Wir fügen eine Methode zur weiteren Analyse hinzu:

public class StackOneTest {
    
    
	
	public static int add(int a, int b) {
    
    
		return a+b;
	}
	
	public static void main(String[] args) {
    
    
		int a = 1;
		int b = 2;
		int c = add(a, b);
		System.out.println(c);
	}
}

Fügen Sie hier eine Bildbeschreibung ein

Von hier aus können wir sehen, dass invokestatices sich um den Befehlssatz zum Aufrufen der Methode handeln sollte. Seine Funktionsweise ähnelt der vorherigen, Sie müssen jedoch auf TreturnAnweisungen mit unterschiedlichen Rückgabewerttypen achten (T ist ein Codename, der von Generics entlehnt ist).

Multiple-Stack-Bytecode-Analyse

Ändern Sie den Code:

public class StackOneTest {
    
    
	public static void main(String[] args) {
    
    
		int a = 1;
		int b = 2;
		int c = 3;
		int d = a+b;
		int e = a+c;
	}
}

Analysieren Sie auf die gleiche Weise:

Fügen Sie hier eine Bildbeschreibung ein

Es kann festgestellt werden, dass der Bytecode auch zeilenweise bearbeitet wird. Die Verarbeitung von Variablen dund ePositionen ähnelt der ersten Stelle, außer dass asie wiederholt in den Stapel verschoben wird.

Supongo que te gusta

Origin blog.csdn.net/m0_74220316/article/details/135176962
Recomendado
Clasificación