The Artima Developer Community
Sponsored Link

Try-finally Clauses Defined and Demonstrated
Java Bytecodes that Deal with Finally Clauses
by Bill Venners
First Published in JavaWorld, February 1997

<<  Page 2 of 3  >>

Advertisement

Try-finally clauses in bytecodes
In bytecodes, finally clauses act as miniature subroutines within a method. At each exit point inside a try block and its associated catch clauses, the miniature subroutine that corresponds to the finally clause is called. After the finally clause completes -- as long as it completes by executing past the last statement in the finally clause, not by throwing an exception or executing a return, continue, or break -- the miniature subroutine itself returns. Execution continues just past the point where the miniature subroutine was called in the first place, so the try block can be exited in the appropriate manner.

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:

Finally clauses
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.

Exception table:    from to target type
     0 18 18 any

The bytecodes of the finally clause begin by popping the return address off the stack and storing it into local variable two. At the end of the finally clause, the ret instruction takes its return address from the proper place, local variable two.

<<  Page 2 of 3  >>


Sponsored Links



Google
  Web Artima.com   
Copyright © 1996-2014 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use - Advertise with Us