JVM-Bytecode ist die Befehlssatzarchitektur (ISA) der Java Virtual Machine (JVM), der Sprache, in die Java und anderer JVM-kompatibler Quellcode kompiliert wird . [ 1 ] Jeder Befehl wird durch ein Byte dargestellt , daher der Name Bytecode , wodurch es sich um eine kompakte Datenform handelt . [ 2 ]
Aufgrund der Beschaffenheit von virtuellen Maschinen und Bytecode ist ein JVM-Bytecode- Programm auf jeder Maschine mit einer kompatiblen JVM ausführbar, ohne dass der langwierige Prozess des Kompilierens aus dem Quellcode erforderlich ist.
JVM-Bytecode wird zur Laufzeit entweder von einer JVM interpretiert oder über Just-in-Time -Kompilierung (JIT) in Maschinencode kompiliert und als native Anwendung ausgeführt.
Da JVM-Bytecode für plattformübergreifende Softwarekompatibilität und Sicherheit konzipiert ist , läuft eine JVM-Bytecode-Anwendung in der Regel konsistent auf verschiedenen Hardware- und Softwarekonfigurationen . [ 3 ]
Bezug zu Java
Im Allgemeinen muss ein Java- Programmierer den JVM-Bytecode weder verstehen noch sich dessen bewusst sein. Wie jedoch im IBM developerWorks Journal angemerkt wird: „Das Verständnis von Bytecode und des vom Java-Compiler wahrscheinlich generierten Bytecodes hilft dem Java-Programmierer in ähnlicher Weise wie Assemblerkenntnisse dem C- oder C ++- Programmierer.“ [ 4 ]
Befehlssatzarchitektur
Der Bytecode umfasst verschiedene Befehlstypen, darunter Datenmanipulation, Kontrolltransfer, Objekterzeugung und -manipulation sowie Methodenaufrufe, die alle integraler Bestandteil des objektorientierten Programmiermodells von Java sind . [ 1 ]
Die JVM ist sowohl eine Stack- als auch eine Registermaschine . Jeder Frame eines Methodenaufrufs verfügt über einen „Operandenstapel“ und ein Array von „lokalen Variablen“ [ 5 ] : 2.6 [ 2 ] . Der Operandenstapel dient dazu, Operanden an Berechnungen zu übergeben und den Rückgabewert einer aufgerufenen Methode zu empfangen. Lokale Variablen erfüllen denselben Zweck wie Register und werden ebenfalls zur Übergabe von Methodenargumenten verwendet. Die maximale Größe des Operandenstapels und des Arrays lokaler Variablen, die vom Compiler berechnet wird, ist Teil der Attribute jeder Methode [ 5 ] : 4.7.3. Die Größe kann jeweils unabhängig von 0 bis 65535 Werten variieren, wobei jeder Wert 32 Bit groß ist. longDatentypen double, die 64 Bit groß sind, belegen zwei aufeinanderfolgende lokale Variablen [ 5 ] : 2.6.1 (die im Array lokaler Variablen nicht 64-Bit-ausgerichtet sein müssen) oder einen Wert im Operandenstapel (werden aber als zwei Einheiten in der Stapeltiefe gezählt). [ 5 ] : 2.6.2
Befehlssatz
Jeder Bytecode besteht aus einem Byte, das den Opcode repräsentiert , sowie null oder mehr Bytes für die Operanden. [ 5 ] : 2.11
Von den 256 möglichen Byte-langen Opcodes waren 2015 202 in Gebrauch (~79 %), 51 für zukünftige Verwendung reserviert (~20 %) und 3 Befehle (~1 %) dauerhaft für JVM-Implementierungen reserviert. [ 5 ] : 6.2 Zwei dieser Befehle ( impdep1und impdep2) dienen dazu, Fehler in implementierungsspezifischer Software bzw. Hardware zu erkennen. Der dritte Befehl wird von Debuggern zur Implementierung von Haltepunkten verwendet.
Die Anweisungen lassen sich in mehrere große Gruppen einteilen:
- Laden und speichern (z. B.
aload_0,istore) - Arithmetik und Logik (z. B.
ladd,fcmpl) - Typkonvertierung (z. B.
i2b,d2i) - Objekterzeugung und -manipulation (
new,putfield) - Operandenstapelverwaltung (z. B.
swap,dup2) - Kontrollübertragung (z. B.
ifeq,goto) - Methodenaufruf und Rückgabewert (z. B.
invokespecial,areturn)
Darüber hinaus gibt es einige Anweisungen für eine Reihe spezialisierterer Aufgaben wie das Auslösen von Ausnahmen, die Synchronisierung usw.
Viele Befehle haben Präfixe und/oder Suffixe, die sich auf die Typen der Operanden beziehen, mit denen sie arbeiten. [ 5 ] : 2.11.1 Diese lauten wie folgt:
| Präfix/Suffix | Operandentyp |
|---|---|
i | ganze Zahl |
l | lang |
s | kurz |
b | Byte |
c | Charakter |
f | schweben |
d | doppelt |
a | Referenz |
Beispielsweise iaddaddiert `add` zwei ganze Zahlen, während dadd`add` zwei Gleitkommazahlen addiert. Die Befehle const`add`, load`add` und store`add` können auch ein Suffix der Form `n` annehmen , wobei n eine Zahl von 0 bis 3 für ` add` und `add` ist . Der maximale Wert für `n` variiert je nach Datentyp. _nloadstoreconst
Die constBefehle legen einen Wert des angegebenen Typs auf den Stack. Beispielsweise iconst_5legt `push` eine Ganzzahl (32-Bit-Wert) mit dem Wert 5 auf den Stack, während ` dconst_1push` eine Gleitkommazahl (64-Bit-Wert) mit dem Wert 1 auf den Stack legt. Es gibt auch `push` aconst_null, das eine nullReferenz auf den Stack legt. Das `n` bei den Befehlen `push` loadund store`push` gibt den Index im lokalen Variablenarray an, von dem der Wert geladen bzw. in den der Wert gespeichert werden soll. Der aload_0Befehl `push` legt das Objekt in der lokalen Variable 0 auf den Stack (in der Regel das thisObjekt selbst). `push` istore_1speichert die Ganzzahl oben auf dem Stack in der lokalen Variable 1. Bei lokalen Variablen mit mehr als drei Indizes wird das Suffix weggelassen und es müssen Operanden verwendet werden.
Beispiel
Betrachten Sie den folgenden Java-Code:
outer : for ( int i = 2 ; i < 1000 ; i ++ ) { for ( int j = 2 ; j < i ; j ++ ) { if ( i % j == 0 ) continue outer ; } System . out . println ( i ); }Ein Java-Compiler könnte den obigen Java-Code wie folgt in Bytecode übersetzen, vorausgesetzt, der obige Code befindet sich in einer Methode:
0 : iconst_2 1 : istore_1 2 : iload_1 3 : sipush 1000 6 : if_icmpge 44 9 : iconst_2 10 : istore_2 11 : iload_2 12 : iload_1 13 : if_icmpge 31 16 : iload_1 17 : iload_2 18 : irem 19 : ifne 25 22 : goto 38 25 : iinc 2, 1 28 : goto 11 31 : getstatic #84; // Field java/lang/System.out : Ljava/io/PrintStream; 34 : iload_1 35 : invokevirtual #85 ; // Methode java/io/PrintStream.println:(I)V 38 : iinc 1, 1 41 : goto 2 44 : returnGeneration
Die am häufigsten verwendete Sprache, die JVM-Bytecode erzeugt, ist Java. Ursprünglich gab es nur einen Compiler, den javac -Compiler von Sun Microsystems , der Java-Quellcode in JVM-Bytecode übersetzte . Da mittlerweile jedoch alle Spezifikationen für JVM-Bytecode verfügbar sind, bieten auch andere Hersteller Compiler an, die JVM-Bytecode erzeugen. Beispiele hierfür sind:
- Eclipse-Compiler für Java (ECJ)
- Jikes kompiliert von Java in JVM-Bytecode (entwickelt von IBM , implementiert in C++ )
- Espresso kompiliert von Java zu JVM-Bytecode (nur Java 1.0)
- Der GNU Compiler for Java (GCJ) kompiliert von Java in JVM-Bytecode; er kann auch in nativen Maschinencode kompilieren und war bis Version 6 Teil der GNU Compiler Collection (GCC).
Einige Projekte stellen Java-Assembler bereit, um das manuelle Schreiben von JVM-Bytecode zu ermöglichen. Assembler-Code kann aber auch maschinell generiert werden, beispielsweise von einem Compiler für die Java Virtual Machine. Zu den bekanntesten Java-Assemblern gehören:
- Jasmin nimmt Textbeschreibungen für Java-Klassen entgegen, die in einer einfachen Assembler-ähnlichen Syntax unter Verwendung des Befehlssatzes der Java Virtual Machine geschrieben sind, und generiert daraus eine Java-Klassendatei [ 6 ].
- Jamaica ist eine Makroassemblersprache für die Java Virtual Machine. Die Java-Syntax wird zur Definition von Klassen oder Schnittstellen verwendet. Methodenrümpfe werden mithilfe von Bytecode-Anweisungen spezifiziert. [ 7 ]
- Krakatau Bytecode Tools enthält derzeit drei Werkzeuge: einen Dekompiler und Disassembler für Java-Klassendateien sowie einen Assembler zum Erstellen von Klassendateien. [ 8 ]
- Lilac, ein Assembler und Disassembler für die Java Virtual Machine. [ 9 ]
Andere haben Compiler für verschiedene Programmiersprachen entwickelt, die auf die Java Virtual Machine abzielen, wie zum Beispiel:
- ColdFusion
- JRuby und Jython sind zwei Skriptsprachen , die auf Ruby und Python basieren.
- Apache Groovy ist eine optional typisierte und dynamische Allzwecksprache mit statischer Typisierung und statischer Kompilierung.
- Scala ist eine typsichere, universelle Programmiersprache, die objektorientierte und funktionale Programmierung unterstützt.
- JGNAT und AppletMagic kompilieren Code aus der Sprache Ada in JVM-Bytecode.
- C-zu-Java-Bytecode-Compiler
- Clojure ist eine funktionale, unveränderliche Allzwecksprache aus der Lisp- Familie mit starkem Fokus auf Nebenläufigkeit.
- Kawa , eine Implementierung der Scheme -Sprache, die gleichzeitig ein Dialekt von Lisp ist.
- MIDletPascal
- JavaFX-Script -Code wird in JVM-Bytecode kompiliert.
- Kotlin ist eine statisch typisierte Allzwecksprache mit Typinferenz.
- Der Object-Pascal- Quellcode wird mit dem Free Pascal 3.0+ Compiler in JVM-Bytecode kompiliert. [ 10 ] [ 11 ]
Ausführung
Es gibt heutzutage verschiedene Java Virtual Machines (JVM) zur Ausführung von JVM-Bytecode, sowohl kostenlose als auch kommerzielle Produkte. Ist die Ausführung von Bytecode in einer virtuellen Maschine unerwünscht, kann ein Entwickler Java-Quellcode oder Bytecode auch direkt in nativen Maschinencode kompilieren, beispielsweise mit dem GNU Compiler for Java (GCJ). Einige Prozessoren können JVM-Bytecode nativ ausführen. Solche Prozessoren werden als Java-Prozessoren bezeichnet .
Unterstützung für dynamische Sprachen
Die Java Virtual Machine bietet eine gewisse Unterstützung für dynamisch typisierte Sprachen . Der Großteil des vorhandenen JVM-Befehlssatzes ist statisch typisiert – in dem Sinne, dass Methodenaufrufe zur Kompilierzeit auf ihre Typsignaturen überprüft werden , ohne dass ein Mechanismus vorhanden ist, um diese Entscheidung auf die Laufzeit zu verschieben oder die Methodenaufrufmethode auf alternative Weise zu wählen. [ 12 ]
Die Java Specification Request ( JSR ) 292 ( Unterstützung dynamisch typisierter Sprachen auf der Java-Plattform ) [ 13 ] führte eine neue invokedynamicAnweisung auf JVM-Ebene ein, um Methodenaufrufe mit dynamischer Typüberprüfung (anstelle der bisherigen statisch typisierten invokevirtualAnweisung) zu ermöglichen. Die Da Vinci Machine ist eine prototypische Implementierung einer virtuellen Maschine, die JVM-Erweiterungen zur Unterstützung dynamischer Sprachen hostet. Alle JVMs, die die Java Platform, Standard Edition (JSE) 7 unterstützen, enthalten ebenfalls den invokedynamicentsprechenden Opcode.
Siehe auch
- Bytecode-Engineering-Bibliothek
- Common Intermediate Language (CIL), Microsofts Konkurrenzprodukt zum JVM-Bytecode
- Java-Backporting-Tools
- Java-Klassendatei
- Java Virtual Machine
- JStik
- ObjectWeb ASM
- Liste der JVM-Bytecode-Anweisungen
- Liste der JVM-Sprachen
Referenzen
- ^ a b „Java Virtual Machine Specification“ . Oracle . Abgerufen am 14. November 2023 .
- ^ a b Lindholm, Tim (2015). Die Java Virtual Machine Spezifikation . Oracle. ISBN 978-0133905908Die
- ^ Arnold, Ken (1996). „Die Programmiersprache Java“. Sun Microsystems . 1 (1): 30–40 .
- ^ „IBM Developer“ . developer.ibm.com . Abgerufen am 20. Februar 2006 .
- ^ a b c d e f g Lindholm, Tim; Yellin, Frank; Bracha, Gilad; Buckley, Alex (13. Februar 2015). Die Java Virtual Machine Spezifikation (Java SE 8 ed.).
- ^ „Jasmin-Homepage“ . jasmin.sourceforge.net . Abgerufen am 2. Juni 2024 .
- ^ Huang, James Jianbo. „Jamaika: Der Makroassembler der Java Virtual Machine (JVM)“ . JavaWorld . Archiviert vom Original am 14. November 2023. Abgerufen am 2. Juni 2024 .
- ^ „Storyyeller/Krakatau“ . 1. Juni 2024 . Abgerufen am 2. Juni 2024 – über GitHub.
- ^ „Lilac – ein Java-Assembler“ . lilac.sourceforge.net . Abgerufen am 2. Juni 2024 .
- ^ „FPC Neue Funktionen 3.0.0“ . Free Pascal Wiki . Abgerufen am 2. Juni 2024 .
- ^ „FPC JVM – Free Pascal Wiki“ . wiki.freepascal.org . Abgerufen am 2. Juni 2024 .
- ^ Nutter, Charles (3. Januar 2007). „InvokeDynamic: Tatsächlich nützlich?“ . Abgerufen am 25. Januar 2008 .
- ^ „Das Java Community Process(SM)-Programm – JSRs: Java Specification Requests – Detail JSR# 292“ . www.jcp.org . Abgerufen am 2. Juni 2024 .
Externe Links
- Oracles Java Virtual Machine Spezifikation
- Programmiersprachen für die Java Virtual Machine
- Bytecode Visualizer – Bytecode-Viewer und -Debugger (kostenloses Eclipse-Plugin)
- AdaptJ StackTrace – Debugging auf Bytecode-Ebene mit vollständiger Kontrolle über den Stack, die lokalen Variablen und den Ausführungsablauf
- Java Class Unpacker – ein Plugin für Total Commander, mit dem sich Klassendateien als komprimierte Archive öffnen und Felder sowie Methoden als separate Dateien anzeigen lassen. Der Bytecode kann mit F3 als Text dargestellt werden.