Thursday, May 05, 2005

Implementing "finally"

Java has this great facility when dealing with exceptions called "finally". When you have a finally block, any code within it will execute regardless of whether an exception has been thrown or not. It's a good place to put code that must absolutely positively be run like closing files, database connections and any other similar resources. C# also has it and I believe it's also been standardized in C++, but not sure. Anyway, ever wonder how it is actually implemented?

I wrote a really simple method that just has a try, catch, finally block and examined the bytecode generated using this really cool Eclipse plug-in.

Here's the java code...

public void testFinally()
{
    try
    {
        System.out.println( "try" );
    }
    catch ( Exception ex )
    {
        System.out.println( "catch" );
    }
    finally
    {
        System.out.println( "finally" );
    }
}


and here's the bytecode...

// testFinally()V
L0 (7)
GETSTATIC java/lang/System.out: Ljava/io/PrintStream;
LDC "try"
INVOKEVIRTUAL java/io/PrintStream.println(Ljava/lang/String;)V
GOTO L1
L2 (9)
ASTORE 1
L3 (11)
GETSTATIC java/lang/System.out: Ljava/io/PrintStream;
LDC "catch"
INVOKEVIRTUAL java/io/PrintStream.println(Ljava/lang/String;)V
L4
GOTO L1
L5 (14)
ASTORE 3
JSR L6
ALOAD 3
ATHROW
L6
ASTORE 2
L7 (15)
GETSTATIC java/lang/System.out: Ljava/io/PrintStream;
LDC "finally"
INVOKEVIRTUAL java/io/PrintStream.println(Ljava/lang/String;)V
L8 (16)
RET 2
L1 (14)
JSR L6
L9 (17)
RETURN
L10
LOCALVARIABLE this Ltest; L0 L10 0
LOCALVARIABLE ex Ljava/lang/Exception; L3 L4 1
MAXSTACK = 2
MAXLOCALS = 4


I don't understand bytecode. I would be interested in knowing more about it. But anyway, you don't need to be a bytecode specialist to understand how "finally" is always executed regardless of exception or not...

This is the "try" part

L0 (7)
GETSTATIC java/lang/System.out: Ljava/io/PrintStream;
LDC "try"
INVOKEVIRTUAL java/io/PrintStream.println(Ljava/lang/String;)V
GOTO L1


and this is the "catch" part

L3 (11)
GETSTATIC java/lang/System.out: Ljava/io/PrintStream;
LDC "catch"
INVOKEVIRTUAL java/io/PrintStream.println(Ljava/lang/String;)V
L4
GOTO L1


Notice any similarities? What is at "L1"?

L1 (14)
JSR L6


JSR is a jump instruction. So all this does is just to location L6. As you would suspect, it is the finally block...

L6
ASTORE 2
L7 (15)
GETSTATIC java/lang/System.out: Ljava/io/PrintStream;
LDC "finally"
INVOKEVIRTUAL java/io/PrintStream.println(Ljava/lang/String;)V


Voila. Mystery solved!

I have to say that the bytecode is NOT the most diffcult thing in the world to interpret. A lot of it is pretty self-explanatory. I realize this is a real toy example and it gets more complicated, but it looks like something one can pick up. It's the same situation with IL code in the .NET world. I think maybe IL is a bit easier to read.

Maybe not the most important thing, but something that's interesting and nice to know.

No comments: