Recommended: Sing it, brah! 5 fabulous songs for developers
JW's Top 5
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 2 of 4
The opcode that causes the JVM to jump to a miniature subroutine is the jsr instruction. The jsr instruction takes a two-byte operand, the offset from the location of the jsr instruction where the miniature subroutine begins. A second variant of the jsr instruction is jsr_w, which performs the same function as jsr but takes a wide (four-byte) operand. When the JVM encounters a jsr or jsr_w instruction, it pushes a return address onto the stack, then continues execution at the start of the miniature subroutine. The return address is the offset of the bytecode immediately following the jsr or jsr_w instruction and its operands.
After a miniature subroutine completes, it invokes the ret instruction, which returns from the subroutine. The ret instruction takes one operand, an index into the local variables where the return address is stored. The opcodes that deal
with finally clauses are summarized in the following table:
| Opcode | Operand(s) | Description |
|---|---|---|
jsr |
branchbyte1, branchbyte2 | pushes the return address, branches to offset |
jsr_w |
branchbyte1, branchbyte2, branchbyte3, branchbyte4 | pushes the return address, branches to wide offset |
ret |
index | returns to the address stored in local variable index |
Don't confuse a miniature subroutine with a Java method. Java methods use a different set of instructions. Instructions such
as invokevirtual or invokenonvirtual cause a Java method to be invoked, and instructions such as return, areturn, or ireturn cause a Java method to return. The jsr instruction does not cause a Java method to be invoked. Instead, it causes a jump to a different opcode within the same method.
Likewise, the ret instruction doesn't return from a method; rather, it returns back to the opcode in the same method that immediately follows
the calling jsr instruction and its operands. The bytecodes that implement a finally clause are called a miniature subroutine because they act like a small subroutine within the bytecode stream of a single
method.
You might think that the ret instruction should pop the return address off the stack, because that is where it was pushed by the jsr instruction. But it doesn't. Instead, at the start of each subroutine, the return address is popped off the top of the stack
and stored in a local variable -- the same local variable from which the ret instruction later gets it. This asymmetrical manner of working with the return address is necessary because finally clauses
(and therefore, miniature subroutines) themselves can throw exceptions or include return, break, or continue statements. Because of this possibility, the extra return address that was pushed onto the stack by the jsr instruction must be removed from the stack right away, so it won't still be there if the finally clause exits with a break, continue, return, or thrown exception. Therefore, the return address is stored into a local variable at the start of any finally clause's miniature subroutine.
As an illustration, consider the following code, which includes a finally clause that exits with a break statement. The result of this code is that, irrespective of the parameter bVal passed to method
surpriseTheProgrammer(), the method returns false:
static boolean surpriseTheProgrammer(boolean bVal) {
while (bVal) {
try {
return true;
}
finally {
break;
}
}
return false;
}
The example above shows why the return address must be stored into a local variable at the beginning of the finally clause. Because the finally clause exits with a break, it never executes the ret instruction. As a result, the JVM never goes back to finish up the "return true" statement. Instead, it just goes ahead with the break and drops down past the closing curly brace of the while statement. The next statement is "return false," which is precisely what the JVM does.
The behavior shown by a finally clause that exits with a break is also shown by finally clauses that exit with a return or continue, or by throwing an exception. If a finally clause exits for any of these reasons, the ret instruction at the end of the finally clause is never executed. Because the ret instruction is not guaranteed to be executed, it can't be relied on to remove the return address from the stack. Therefore,
the return address is stored into a local variable at the beginning of the finally clause's miniature subroutine.
For a complete example, consider the following method, which contains a try block with two exit points. In this example, both exit points are return statements:
static int giveMeThatOldFashionedBoolean(boolean bVal) {
try {
if (bVal) {
return 1;
}
return 0;
}
finally {
System.out.println("Got old fashioned.");
}
}
The above method compiles to the following bytecodes:
// The bytecode sequence for the try block:
0 iload_0 // Push local variable 0 (arg passed as divisor)
1 ifeq 11 // Push local variable 1 (arg passed as dividend)
4 iconst_1 // Push int 1
5 istore_3 // Pop an int (the 1), store into local variable 3
6 jsr 24 // Jump to the mini-subroutine for the finally clause
9 iload_3 // Push local variable 3 (the 1)
10 ireturn // Return int on top of the stack (the 1)
11 iconst_0 // Push int 0
12 istore_3 // Pop an int (the 0), store into local variable 3
13 jsr 24 // Jump to the mini-subroutine for the finally clause
16 iload_3 // Push local variable 3 (the 0)
17 ireturn // Return int on top of the stack (the 0)
// The bytecode sequence for a catch clause that catches any kind of exception
// thrown from within the try block.
18 astore_1 // Pop the reference to the thrown exception, store
// into local variable 1
19 jsr 24 // Jump to the mini-subroutine for the finally clause
22 aload_1 // Push the reference (to the thrown exception) from
// local variable 1
23 athrow // Rethrow the same exception
// The miniature subroutine that implements the finally block.
24 astore_2 // Pop the return address, store it in local variable 2
25 getstatic #8 // Get a reference to java.lang.System.out
28 ldc #1 // Push <String "Got old fashioned."> from the constant pool
30 invokevirtual #7 // Invoke System.out.println()
33 ret 2 // Return to return address stored in local variable 2
The bytecodes for the try block include two jsr instructions. Another jsr instruction is contained in the catch clause. The catch clause is added by the compiler because if an exception is thrown during the execution of the try block, the finally block must still be executed. Therefore, the catch clause merely invokes the miniature subroutine that represents the finally clause, then throws the same exception again. The exception table for the giveMeThatOldFashionedBoolean() method, shown below, indicates that any exception thrown between and including addresses 0 and 17 (all the bytecodes that
implement the try block) are handled by the catch clause that starts at address 18.