4.13. JVM-Befehlssatz: Kurzbeschreibung J
Führe eine Sprung zu einem Unterprogramm (Subroutine) aus.
Durch die Anweisung erfolgt ein Sprung an die Stelle im Programm, die sich
aus einem Offset ergibt. Der vorzeichenbehaftete Offset berechnet sich zu:
(branchbyte1 << 8) | branchbyte2.
Die Anweisung jsr
kann von einem Java-Compiler erzeugt werden, wenn
eine finally
-Klausel innerhalb des Java-Quelltextes enthalten ist
(im Zusammenhang mit dem Befehl
ret
).
Beispiel:
Der weiter unten stehende Java-Quelltext enthält eine
try
-catch
-finally
-Anweisung. Der
finally
-Block wird im Beispiel immer aufgerufen, unabhängig davon
ob eine Ausnahme ausgelöst wurde oder nicht (es wird z.B. eine
NumberFormatException
ausgelöst, wenn "0011" durch "0011x" im
Quelltext ersetzt wird). Die zum Quellprogramm korrespondierenden JVM-Befehle
wurden durch den Open-Source-Compiler
Jikes
in der Version 1.22 erzeugt. Die Übersetzung der
try
-catch
-finally
-Anweisung in Bytecode
wurde mit Hilfe von jsr
und ret
gelöst. Die Verwendung
dieser beiden JVM-Befehle ist aber nicht zwingend erforderlich. Bei der Compilierung
des Beispiels mit dem im JDK 6 enthaltenen Java-Compiler javac
(Version
1.6.0) werden jsr
und ret
nicht innerhalb des erzeugten
Bytecodes verwendet.
Auf der linken Seite der folgenden Übersicht befindet sich der Java-Quelltext
und rechts daneben stehen die einzelnen JVM-Befehle, die durch den Java-Compiler
Jikes erzeugt wurden. Im unteren Teil der Übersicht befindet sich die
Exception-Tabelle, die zur Behandlung von möglicherweise erzeugten Ausnahmen nötig
ist. Die Daten für diese Tabelle sind im Methodenbereich einer Klassendatei an
entsprechender Stelle codiert und können ebenfalls mit Hilfe des Java-Disassemblers
javap
(Option -c
) zur Anzeige gebracht werden. Im
Anschluss an die Übersicht soll der Programmablauf anhand der erzeugten
JVM-Befehle erläutert werden.
(compiliert mit Jikes, Ver. 1.22)
int a = 0; 0: iconst_0
1: istore_1
int i = 0; 2: iconst_0
3: istore_2
try { 4: ldc #10; //String 0011
i = Integer.parseInt("0011", 2); 6: iconst_2
7: invokestatic #16;
//Method java/lang/Integer.
parseInt:(Ljava/lang/String;I)I
10: istore_2
11: goto 35
} catch (NumberFormatException e) { 14: astore 5
a++; 16: iinc 1,1
19: goto 35
22: astore_3
23: jsr 28
26: aload_3
27: athrow
} finally { 28: astore 4
a--; 30: iinc 1,-1
} 33: ret 4
35: jsr 28
38: return
Exception-Tabelle:
from to target type
4 11 14 Class java/lang/NumberFormatException
4 26 22 any
35 38 22 any
Der try
-Block beginnt mit den beiden Anweisungen ldc #10
und 6:
iconst_2
, die benötigte Parameter für den Aufruf
der statischen Methode parseInt
auf den Operandenstapel legen. Der
Methodenaufruf wird dann mit der Anweisung invokestatic #16
realisiert.
Der weitere Programmfluss hängt nun davon ab, ob die aufgerufene Methode eine
NumberFormatException
auslöst oder ob der übergebene String (im
Beispiel "0011") unter Vorgabe der Radix 2 in eine Integer-Zahl umgewandelt
werden kann.
Im Beispiel erfolgt kein Auslösen der Exception und mit 11:
goto 35
wird der aktuelle Block verlassen und an Stelle 35 im Bytecode
gesprungen. Mit 35:
jsr 28
wird zur Subroutine
(finally
-Block), die bei Bytecodestelle 28 beginnt, gesprungen. Der
auf diesen Sprungbefehl folgende Opcode ist b1
für return
und steht an Stelle 38. Der Wert dieser Stelle im Bytecode wird für den Rücksprung
nach Beendigung der Subroutine benötigt und wird daher auf dem Operandenstapel
zwischengespeichert (Typ returnAddress
). Der erste Befehl der
Subroutine astore 4
legt diesen obersten Wert des Stapels im Array
der lokalen Variablen bei Index 4 ab. Anschließend wird der Wert der Variablen
a
um 1 vermindert. Mit ret 4
wird die Subroutine wieder
verlassen, indem die Rücksprungadresse aus dem Array der lokalen Variablen bei
Index 4 wieder ausgelesen und in das Register des Programmzählers (PC) geschrieben
wird. Das Programm wird also an Bytecodestelle 38 mit der Anweisung
return
(letzter Befehl der main
-Methode, aus der der
Quelltext stammt) fortgesetzt.
Falls die Methode parseInt
eine NumberFormatException
auslöst, wird goto 35
an Bytecodestelle 11 nicht erreicht, sondern es
wird an die Stelle 14 gesprungen (wie in der Exception-Tabelle angegeben: target 14).
Bei dieser Stelle beginnt der catch
-Block, der die erzeugte Ausnahme
behandelt. Dieser Block wird schließlich mit 19:
goto 35
verlassen, was zu einer Abarbeitung des finally
-Blocks führt (siehe
oben).
Springe zu einem Unterprogramm (erweiterter Verzweigungsoffset).
Die Verwendung des Befehls
jsr
ermöglicht
relative Sprünge von -32768 bzw. +32767, ausgehend von der Bytecodestelle des
Opcodes des Sprungbefehls. Mit jsr_w
steht ein erweiterter
Verzweigungsoffset zur Verfügung, da bei diesem Befehl vier Verzweigungsbytes zur
Verfügung stehen. Der vorzeichenbehaftete 32-Bit-Offset berechnet sich dabei wie
folgt: (branchbyte1 << 24) | (branchbyte2 <<
16) | (branchbyte3 << 8) | branchbyte4.
Beispiel: Siehe dazu
jsr
.