Try-finally clauses defined and demonstrated

Through code samples, tables, and a Java virtual machine simulation, this article examines the bytecodes of the Java virtual machine that deal with finally clauses

Welcome to another installment of Under The Hood. This column gives Java developers a glimpse of the mysterious mechanisms clicking and whirring beneath their running Java programs. This month's article continues the discussion of the bytecode instruction set of the Java virtual machine (JVM). Its focus is the manner in which the JVM handles finally clauses and the bytecodes that are relevant to these clauses.

Finally: Something to cheer about

As the Java virtual machine executes the bytecodes that represent a Java program, it may exit a block of code -- the statements between two matching curly braces -- in one of several ways. For one, the JVM simply could execute past the closing curly brace of the block of code. Or, it could encounter a break, continue, or return statement that causes it to jump out of the block of code from somewhere in the middle of the block. Finally, an exception could be thrown that causes the JVM either to jump to a matching catch clause, or, if there isn't a matching catch clause, to terminate the thread. With these potential exit points existing within a single block of code, it is desirable to have an easy way to express that something happened no matter how a block of code is exited. In Java, such a desire is expressed with a try-finally clause.

To use a try-finally clause:

  • enclose in a try block the code that has multiple exit points, and

  • put in a finally block the code that must happen no matter how the try block is exited.

For example:

try {
      // Block of code with multiple exit points
}
finally {
      // Block of code that is always executed when the try block is exited,
      // no matter how the try block is exited
}

If you have any catch clauses associated with the try block, you must put the finally clause after all the catch clauses, as in:

try {
      // Block of code with multiple exit points
}
catch (Cold e) {
      System.out.println("Caught cold!");
}
catch (APopFly e) {
      System.out.println("Caught a pop fly!");
}
catch (SomeonesEye e) {
      System.out.println("Caught someone's eye!");
}
finally {
      // Block of code that is always executed when the try block is exited,
      // no matter how the try block is exited.
      System.out.println("Is that something to cheer about?");
}

If during execution of the code within a try block, an exception is thrown that is handled by a catch clause associated with the try block, the finally clause will be executed after the catch clause. For example, if a Cold exception is thrown during execution of the statements (not shown) in the try block above, the following text would be written to the standard output:

Caught cold!
Is that something to cheer about?

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
OpcodeOperand(s)Description
jsrbranchbyte1, branchbyte2pushes the return address, branches to offset
jsr_wbranchbyte1, branchbyte2, branchbyte3, branchbyte4pushes the return address, branches to wide offset
retindexreturns 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.

HopAround: A Java virtual machine simulation

The applet below demonstrates a Java virtual machine executing a sequence of bytecodes. The bytecode sequence in the simulation was generated by the javac compiler for the hopAround() method of the class shown below:

class Clown {
    static int hopAround() {
        int i = 0;
        while (true) {
            try {
                try {
                    i = 1;
                }
                finally {    // the first finally clause
                    i = 2;
                }
                i = 3;
                return i;    // this never completes, because of the continue
            }
            finally {        // the second finally clause
                if (i == 3) {
                    continue; // this continue overrides the return statement
                }
            }
        }
    }
}

The bytecodes generated by javac for the hopAround() method are shown below:

   0 iconst_0             // Push constant 0
   1 istore_0             // Pop into local var 0: int i = 0;
                          // Both try blocks start here (see exception table, below).
   2 iconst_1             // Push constant 1
   3 istore_0             // Pop into local var 0: i = 1;
   4 jsr 16               // Jump to mini-subroutine at offset 16 (the first finally clause) 
   7 goto 22              // Jump to offset 22 (to just below first finally clause)
                          // Catch clause for the first finally clause:
  10 astore_3             // Pop the reference to thrown exception, store in local variable 3
  11 jsr 16               // Jump to mini-subroutine at offset 16 (the first finally clause)
  14 aload_3              // Push the reference (to thrown exception) from local variable 3
  15 athrow               // Rethrow the same exception
                          // The first finally clause:
  16 astore 4             // Store the return address in local variable 4
  18 iconst_2             // Push constant 2
  19 istore_0             // Pop into local var 0: i = 2;
  20 ret 4                // Jump to return address stored in local variable 4
                          // Bytecodes for the code just after the first finally clause:
  22 iconst_3             // Push constant 3
  23 istore_0             // Pop into local var 0: int i = 3;
                          // Bytecodes for the return statment:
  24 iload_0              // Push the int from local variable 0 (i, which is 3)
  25 istore_3             // Pop and store the int into local variable 3 (the return value, i)
  26 jsr 37               // Jump to mini-subroutine at offset 37 (the second finally clause)
  29 iload_3              // Push the int from local variable 3 (the return value)
  30 ireturn              // Return the int on the top of the stack
                          // Catch clause for the second finally clause:
  31 astore_1             // Pop the reference to thrown exception, store in local variable 1
  32 jsr 37               // Jump to mini-subroutine at offset 37 (the second finally clause)
  35 aload_1              // Push the reference (to thrown exception) from local variable 1
  36 athrow               // Rethrow the same exception
                          // The second finally clause:
  37 astore_2             // Store the return address in local variable 2
  38 iload_0              // Push the int from local variable 0 (i)
  39 iconst_3             // Push constant 3
  40 if_icmpne 46         // If the top two ints on the stack are unequal, jump to offset 46: if (i == 3) {
  43 goto 2               // Jump to offset 2 (the top of the while block): continue;
  46 ret 2                // Jump to return address stored in local variable 2
Exception table:
   from   to  target type
     2     4    10   any
     2    31    31   any

The hopAround() method returns from the first finally clause by executing past the closing curly brace, but returns from the second finally clause by executing a continue statement. The first finally clause, therefore, exits via its ret instruction. But because the second finally clause exits via a continue, its ret instruction is never executed. The continue statement causes the JVM to jump to the top of the while loop again. This results in an endless loop, even though it is a return statement that originally causes the second finally clause to be executed in the first place. The continue statement in the finally clause supersedes the return statement, so the method never returns.

Note that the bytecodes that implement the return statement store a copy of the return value into local variable 3 before jumping to the miniature subroutine that represents the second finally clause. Then, after the miniature subroutine returns (in this case it never does, because the continue is always executed), the return value is retrieved from local variable 3 and returned. This highlights the way the JVM returns values when finally clauses are also executed. Rather than returning the value of i after the finally clause is executed, the JVM will return the value that i had just before the finally clause was executed. This means that even if the finally clause changes the value of i, the method will still return the value that i had when the return statement was first reached, before the finally clause was invoked. If you wanted the finally clause to be able to change the return value of the method, you would have to put an actual return statement with the new return value into the finally clause itself.

To drive the simulation, just press the Step button. Each press of the Step button will cause the Java virtual machine to execute one bytecode instruction. To start the simulation over, press the Reset button. To cause the Java virtual machine to repeatedly execute bytecodes with no further coaxing on your part, press the Run button. The Java virtual machine will then execute the bytecodes until the Stop button is pressed. The text area at the bottom of the applet describes the next instruction to be executed. Happy clicking.

Bill Venners has been writing software professionally for 12 years. Based in Silicon Valley, he provides software consulting and training services under the name Artima Software Company. Over the years he has developed software for the consumer electronics, education, semiconductor, and life insurance industries. He has programmed in many languages on many platforms: assembly language on various microprocessors, C on Unix, C++ on Windows, Java on the Web. He is author of the book: Inside the Java Virtual Machine, published by McGraw-Hill.

Learn more about this topic

  • Previous Under The Hood articles
  • The lean, mean virtual machine -- Gives an introduction to the Java virtual machine. Look here to see how the garbage collected heap fits in with the other parts of the Java virtual machine.
  • The Java class file lifestyle -- Gives an overview to the Java class file, the file format into which all Java programs are compiled.
  • Java's garbage-collected heap -- Gives an overview of garbage collection in general and the garbage-collected heap of the Java virtual machine in particular.
  • Bytecode basics -- Introduces the bytecodes of the Java virtual machine, and discusses primitive types, conversion operations, and stack operations in particular.
  • Floating Point Arithmetic -- Describes the Java virtual machine's floating-point support and the bytecodes that perform floating-point operations.
  • Logic and Arithmetic -- Describes the Java virtual machine's support for logical and integer arithmetic, and the related bytecodes.
  • Objects and Arrays -- Describes how the Java virtual machine deals with objects and arrays, and discusses the relevant bytecodes.
  • Exceptions -- Describes how the Java virtual machine deals with exceptions, and discusses the relevant bytecodes.

Join the discussion
Be the first to comment on this article. Our Commenting Policies