Eine Java Virtual Machine-Anweisung besteht aus einem Byte-langen Operationscode (Opcode), der eine bestimmte Bedeutung darstellt, gefolgt von null oder mehr Operanden, die die Operationsparameter darstellen. Viele Anweisungen in der virtuellen Maschine enthalten keine Operanden, sondern nur einen Opcode. Wenn die Ausnahme ignoriert wird, kann der JVM-Interpreter effektiv mit nur einem Code arbeiten.
Kopieren Sie den Codecode wie folgt:
Tun{
Berechnen Sie automatisch das PC-Register und rufen Sie den Opcode vom Speicherort des PC-Registers ab
if(Operand existiert) nimm den Operanden heraus;
Führen Sie die durch den Opcode definierte Operation aus;
}while (die nächste Schleife verarbeiten)
Die Anzahl und Länge der Operanden hängt vom Opcode ab. Wenn die Länge eines Operanden ein Byte überschreitet, wird er in der Big-Endian-Reihenfolge (erster High-End-Bytecode) gespeichert und sein Wert sollte (byte1<<8) | sein Byte2.
Der Bytecode-Befehlsstrom ist Einzelbyte-ausgerichtet, mit Ausnahme der „tableswitch“- und „lookupswitch“-Befehle. Ihre Operanden sind durch 4 Bytes geteilt. Zur Erzielung einer Ausrichtung müssen entsprechende Lücken reserviert werden.
Durch die Begrenzung der Länge des Opcodes der Java Virtual Machine auf ein Byte und den Verzicht auf die Parameterlängenausrichtung des kompilierten Codes erhält man einen kurzen und schlanken kompilierten Code, auch wenn dies der JVM-Implementierung möglicherweise gewisse Leistungseinbußen beschert. Da der Opcode nur ein Byte lang sein kann, begrenzt er die Anzahl der Befehlssätze und geht nicht davon aus, dass die Daten gut ausgerichtet sind. Das heißt, wenn die Daten ein Byte überschreiten, muss die spezifische Datenstruktur neu erstellt werden Die Bytes gehen etwas an Leistung verloren.
Datentypen und Java Virtual Machine
Im Befehlssatz der JVM enthalten die meisten Befehle Datentypinformationen, die ihren Operationen entsprechen. Beispielsweise lädt die Anweisung iload Daten vom Typ int aus der lokalen Variablentabelle in den Operandenstapel, während fload Daten vom Typ float lädt.
Für die meisten Bytecode-Anweisungen, die sich auf Datentypen beziehen, verfügen ihre Opcode-Mnemoniken über Sonderzeichen zur Angabe von: i steht für den Typ int, l steht für long, s steht für short, b steht für Byte, c steht für char, f steht für Float, d steht für Double und a steht für Referenz. Es gibt separate Anweisungen, mit denen bei Bedarf nicht unterstützte Typen in unterstützte Typen konvertiert werden können.
Anweisungen zum Laden und Speichern
Lade- und Speicheranweisungen werden verwendet, um Daten zur und aus der lokalen Variablentabelle des Stapelrahmens und dem Operandenstapel zu übertragen.
1) Zu den Anweisungen zum Laden einer lokalen Variablen in den Operandenstapel gehören: iload, iload_<n>, lload, lload_<n>, float, fload_<n>, dload, dload_<n>, aload, aload_<n>.
2) Anweisungen zum Speichern eines Werts aus dem Operandenstapel in der lokalen Variablen: istore,istore_<n>,lstore,lstore_<n>,fstore,fstore_<n>,dstore,dstore_<n>,astore,astore_<n>
3) Anweisungen zum Laden von Konstanten in den Operandenstapel: bipush, sipush, ldc, ldc_w, ldc2_w, aconst_null,iconst_ml,iconst_<i>, lconst_<l>, fconst_<f>, dconst_<d>
4) Zugriffsindexanweisung der lokalen Variablentabelle: breit
Einige Anweisungen, die mit spitzen Klammern enden, stellen eine Gruppe von Anweisungen dar, z. B. iload_<i>, das iload_0, iload_1 usw. darstellt. Diese Befehlsgruppen sind allgemeine Anweisungen mit einem Operanden.
Bedienungsanleitung
Arithmetische Anweisungen werden verwendet, um eine bestimmte Operation an den Werten auf den beiden Operandenstapeln auszuführen und das Ergebnis wieder oben im Operationsstapel zu speichern.
1) Additionsanweisungen: iadd,ladd,fadd,dadd
2) Subtraktionsanweisungen: isub, lsub, fsub, dsub
3) Multiplikationsanweisungen: imul, lmul, fmul, dmul
4) Divisionsanweisungen: idiv, ldiv, fdiv, ddiv
5) Restanweisungen: irem, lrem, frem, drem
6) Negative Anweisungen: ineg, length, fneg, dneg
7) Verschiebungsanweisungen: ishl,ishr,iusr,lshl,lshr,lusr
8) Bitweise ODER-Anweisungen: ior, lor
9) Bitweise UND-Anweisungen: iand, land
10) Bitweise XOR-Anweisungen: ixor, lxor
11) Inkrementierungsanweisung für lokale Variablen: iinc
12) Vergleichsanweisungen: dcmpg, dcmpl, fcmpg, fcmpl, lcmp
Die virtuelle Java-Maschine legt den Überlauf ganzzahliger Daten nicht eindeutig fest, legt jedoch fest, dass bei der Verarbeitung ganzzahliger Daten nur Divisions- und Restanweisungen dazu führen, dass die virtuelle Maschine eine Ausnahme auslöst, wenn der Divisor 0 ist.
Anweisungen zum Laden und Speichern
Lade- und Speicheranweisungen werden verwendet, um Daten in die und aus der lokalen Variablentabelle des Stapelrahmens und des Operandenstapels zu übertragen.
1) Zu den Anweisungen zum Laden einer lokalen Variablen in den Operandenstapel gehören: iload, iload_<n>, lload, lload_<n>, float, fload_<n>, dload, dload_<n>, aload, aload_<n>.
2) Anweisungen zum Speichern eines Werts aus dem Operandenstapel in der lokalen Variablen: istore,istore_<n>,lstore,lstore_<n>,fstore,fstore_<n>,dstore,dstore_<n>,astore,astore_<n>
3) Anweisungen zum Laden von Konstanten in den Operandenstapel: bipush, sipush, ldc, ldc_w, ldc2_w, aconst_null,iconst_ml,iconst_<i>, lconst_<l>, fconst_<f>, dconst_<d>
4) Zugriffsindexanweisung der lokalen Variablentabelle: breit
Einige Anweisungen, die mit spitzen Klammern enden, stellen eine Gruppe von Anweisungen dar, z. B. iload_<i>, das iload_0, iload_1 usw. darstellt. Diese Befehlsgruppen sind allgemeine Anweisungen mit einem Operanden.
Bedienungsanleitung
Arithmetische Anweisungen werden verwendet, um eine bestimmte Operation an den Werten auf den beiden Operandenstapeln auszuführen und das Ergebnis wieder oben im Operationsstapel zu speichern.
1) Additionsanweisungen: iadd,ladd,fadd,dadd
2) Subtraktionsanweisungen: isub, lsub, fsub, dsub
3) Multiplikationsanweisungen: imul, lmul, fmul, dmul
4) Divisionsanweisungen: idiv, ldiv, fdiv, ddiv
5) Restanweisungen: irem, lrem, frem, drem
6) Negative Anweisungen: ineg, length, fneg, dneg
7) Verschiebungsanweisungen: ishl,ishr,iusr,lshl,lshr,lusr
8) Bitweise ODER-Anweisungen: ior, lor
9) Bitweise UND-Anweisungen: iand, land
10) Bitweise XOR-Anweisungen: ixor, lxor
11) Inkrementierungsanweisung für lokale Variablen: iinc
12) Vergleichsanweisungen: dcmpg, dcmpl, fcmpg, fcmpl, lcmp
Die virtuelle Java-Maschine legt den Überlauf ganzzahliger Daten nicht eindeutig fest, legt jedoch fest, dass bei der Verarbeitung ganzzahliger Daten nur Divisions- und Restanweisungen dazu führen, dass die virtuelle Maschine eine Ausnahme auslöst, wenn der Divisor 0 ist.
Anweisungen zur Typkonvertierung
Typkonvertierungsanweisungen konvertieren zwei numerische Typen der Java Virtual Machine ineinander. Diese Operationen werden im Allgemeinen verwendet, um explizite Typkonvertierungsoperationen im Benutzercode zu implementieren.
JVM unterstützt die erweiterte Typkonvertierung (Typkonvertierung für kleine Bereiche in Typkonvertierung für große Bereiche):
1) Int-Typ zu Long-, Float- und Double-Typ
2) langer Typ zum Floaten, doppelter Typ
3) Float zum Double-Typ
Anweisungen zur Konvertierung schmaler Typen: i2b, i2c, i2s, l2i, f2i, f2l, d2l und d2f können dazu führen, dass die Konvertierungsergebnisse unterschiedliche Vorzeichen und Größenordnungen erzeugen. Wenn beispielsweise der Typ int oder long in den Integer-Typ T konvertiert wird, verwirft der Konvertierungsprozess nur den unerwarteten Inhalt der niedrigsten N Bytes (N ist die Datentyplänge des Typs T).
Objekterstellung und -manipulation
Obwohl Klasseninstanzen und Arrays beide Objekte sind, verwendet die Java Virtual Machine unterschiedliche Bytecode-Anweisungen für die Erstellung und den Betrieb von Klasseninstanzen und Arrays.
1) Anweisung zum Erstellen einer Instanz: neu
2) Anweisungen zum Erstellen von Arrays: newarray, anewarray, multianewarray
3) Zugriffsfeldanweisungen: getfield, putfield, getstatic, putstatic
4) Laden Sie Array-Elemente in die Operandenstapelanweisungen: baload, caload, saload, iaload, laload, faload, daload, aaload
5) Speichern Sie den Wert des Operandenstapels im Array-Element und führen Sie Folgendes aus: bastore,castore,castore,sastore,iastore,fastore,dastore,aastore
6) Holen Sie sich die Array-Längenanweisung: arraylength
7) Überprüfen Sie die Anweisungen zum Instanztyp: Instanz von, Checkcast
Anweisungen zur Operandenstapelverwaltung
Anweisungen, die den Operandenstapel direkt bedienen: pop, pop2, dup, dup2, dup_x1, dup2_x1, dup_x2, dup2_x2 und swap
Kontrollübertragungsanweisung
Ermöglicht der JVM, die Programmausführung bedingt oder bedingungslos ab der Anweisung fortzusetzen, die der angegebenen Anweisung folgt, und nicht mit der Steuerungsübertragungsanweisung. Zu den Kontrollübertragungsanweisungen gehören:
1) Bedingter Zweig: ifeq, iflt, ifle, ifne, ifgt, ifge, ifnull, ifnotnull, if_cmpeq, if_icmpne, if_icmlt, if_icmpgt usw.
2) Zusammengesetzter bedingter Zweig: Tabellenschalter, Suchschalter
3) Unbedingter Zweig: goto,goto_w,jsr,jsr_w,ret
In der JVM gibt es einen speziellen Befehlssatz zur Verarbeitung bedingter Verzweigungsvergleichsoperationen von int- und Referenztypen. Um eindeutig anzuzeigen, ob ein Entitätswert null ist, gibt es spezielle Anweisungen zur Erkennung von Nullwerten. Vergleichsoperationen für bedingte Verzweigungen vom booleschen Typ und Bytetyp, vom Typ char und vom Typ short werden alle mithilfe von Vergleichsanweisungen vom Typ int abgeschlossen, während Vergleichsoperationen für bedingte Verzweigungen vom Typ long, float und double durch Vergleichsoperationsanweisungen des entsprechenden Typs und der entsprechenden Operation ausgeführt werden Die Anweisung gibt einen ganzzahligen Wert zurück, der dem Operandenstapel hinzugefügt wird, und führt dann eine bedingte Vergleichsoperation vom Typ int aus, um den gesamten Verzweigungssprung abzuschließen. Alle Arten von Vergleichen werden schließlich in Vergleichsoperationen vom Typ int konvertiert.
Methodenaufruf- und Rückgabeanweisungen
invokevirtual-Anweisung: Rufen Sie die Instanzmethode des Objekts auf und versenden Sie sie entsprechend dem tatsächlichen Typ des Objekts (Versand der virtuellen Maschine).
invokeinterface-Anweisung: Rufen Sie die Schnittstellenmethode auf, suchen Sie zur Laufzeit nach einem Objekt, das diese Schnittstellenmethode implementiert, und finden Sie die entsprechende aufzurufende Methode.
invokespecial: Rufen Sie Instanzmethoden auf, die eine spezielle Verarbeitung erfordern, einschließlich Instanzinitialisierungsmethoden, private Methoden und übergeordnete Klassenmethoden
invokestatic: Klassenmethode aufrufen (statisch)
Methodenrückgabeanweisungen werden nach der Art des Rückgabewerts unterschieden, einschließlich ireturn (der Rückgabewert ist boolean, byte, char, short und int), lreturn, freturn, drturn und areturn. Die andere Rückgabe gilt für die void-Methode und die Instanzinitialisierungsmethode , Klasse und Die Klasseninitialisierungsmethode i der Schnittstelle wird verwendet.
synchron
Die JVM unterstützt die Synchronisierung auf Methodenebene und die Synchronisierung einer Befehlsfolge innerhalb einer Methode, die beide über Monitore implementiert werden.
Die Synchronisierung auf Methodenebene ist implizit und muss nicht durch Bytecode-Anweisungen gesteuert werden. Sie wird in Methodenaufrufen und Rückgabeoperationen implementiert. Die virtuelle Maschine unterscheidet anhand des ACC_SYNCHRONIZED-Flags in der Methodenstandardstruktur im Methodenkonstantenpool, ob es sich um eine synchrone Methode handelt. Wenn die Methode aufgerufen wird, prüft die aufrufende Anweisung, ob das Flag gesetzt ist. Wenn es gesetzt ist, hält der Ausführungsthread den Monitor, führt dann die Methode aus und gibt den Monitor schließlich frei, wenn die Methode abgeschlossen ist.
Synchronisieren Sie eine Folge von Befehlssätzen, die normalerweise durch einen synchronisierten Block gekennzeichnet sind. Der JVM-Befehlssatz verfügt über Monitorenter und Monitorexit, um synchronisierte Semantik zu unterstützen.
Beim strukturierten Sperren entspricht jeder Monitor-Exit während eines Methodenaufrufs dem vorherigen Monitor-Eintrag. Die JVM stellt sicher, dass strukturierte Sperren durch die folgenden zwei Regeln eingerichtet werden (T steht für einen Thread, M steht für einen Monitor):
1) Die Häufigkeit, mit der T M während der Methodenausführung hält, muss gleich der Häufigkeit sein, mit der T M freigibt, wenn die Methode abgeschlossen ist.
2) Zu keinem Zeitpunkt wird es eine Situation geben, in der T M öfter freigibt, als T M hält.