23

When compiling the following code with a simple try/finally block, the Java Compiler produces the output below (viewed in the ASM Bytecode Viewer):

Code:

try
{
    System.out.println("Attempting to divide by zero...");
    System.out.println(1 / 0);
}
finally
{
    System.out.println("Finally...");
}

Bytecode:

TRYCATCHBLOCK L0 L1 L1 
L0
 LINENUMBER 10 L0
 GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
 LDC "Attempting to divide by zero..."
 INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L2
 LINENUMBER 11 L2
 GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
 ICONST_1
 ICONST_0
 IDIV
 INVOKEVIRTUAL java/io/PrintStream.println (I)V
L3
 LINENUMBER 12 L3
 GOTO L4
L1
 LINENUMBER 14 L1
FRAME SAME1 java/lang/Throwable
 ASTORE 1
L5
 LINENUMBER 15 L5
 GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
 LDC "Finally..."
 INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L6
 LINENUMBER 16 L6
 ALOAD 1
 ATHROW
L4
 LINENUMBER 15 L4
FRAME SAME
 GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
 LDC "Finally..."
 INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L7
 LINENUMBER 17 L7
 RETURN
L8
 LOCALVARIABLE args [Ljava/lang/String; L0 L8 0
 MAXSTACK = 3
 MAXLOCALS = 2

When adding a catch block in between, I noticed that the Compiler copied the finally block 3 times (not posting the bytecode again). This seems like a waste of space in the class file. The copying also doesn't seem to be limited to a maximum number of instructions (similar to how inlining works), since it even duplicated the finally block when I added more calls to System.out.println.


However, the result of a custom compiler of mine that uses a different approach of compiling the same code works exactly the same when executed, but requires less space by using the GOTO instruction:

public static main([Ljava/lang/String;)V
 // parameter  args
 TRYCATCHBLOCK L0 L1 L1 
L0
 GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
 LDC "Attempting to divide by zero..."
 INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
 GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
 ICONST_1
 ICONST_0
 IDIV
 INVOKEVIRTUAL java/io/PrintStream.println (I)V
 GOTO L2
L1
FRAME SAME1 java/lang/Throwable
 POP
L2
FRAME SAME
 GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
 LDC "Finally..."
 INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L3
 RETURN
 LOCALVARIABLE args [Ljava/lang/String; L0 L3 0
 MAXSTACK = 3
 MAXLOCALS = 1

Why does the Java Compiler (or the Eclipse Compiler) copy the bytecode of the finally block multiple times, even using athrow to rethrow exceptions, when the same semantics can be achieved using goto? Is this part of the optimization process, or is my compiler doing it wrong?


(The output in both cases is...)

Attempting to divide by zero...
Finally...
Clashsoft
  • 11,553
  • 5
  • 40
  • 79
  • Is said custom compiler using it's own pseudo-instructions? – Tdorno Mar 15 '15 at 14:25
  • It compiles to Java Bytecode using the ASM framework... – Clashsoft Mar 15 '15 at 14:27
  • Neither of the "answers" so far answer the basic question: **Why** does it duplicate these blocks? – Marco13 Mar 15 '15 at 16:21
  • 4
    `javac` used to use `jsr` (jump subroutine) to only write `finally` code once, but there were some problems related to the new verification using stack map tables. I assume they went back to cloning the code just because it was the easiest thing to do. – Jeffrey Bosboom Mar 15 '15 at 16:23
  • @jeffrey-bosboom looks like it's not a recent change, i'm finding discussions about this starting from 2006. – uraimo Mar 15 '15 at 16:39
  • @uraimo Sounds about right -- verification by stack maps was introduced in Java 6, which came out late 2006, and of course the problem would have been discussed before that. – Jeffrey Bosboom Mar 15 '15 at 16:42
  • Are there any cases where my approach would work differently from the `javac` one? For example, if I had a `try { return 1; } finally { return 2; }`, would `javac` inline the finally block before the `return` statement? (Because I suspect that my bytecode would return `1` in this case) – Clashsoft Mar 15 '15 at 16:46
  • 3
    seems bytecode output of `custom compiler` is incorrect as it `pop` the `throwable` object out in `L1` part. so your function will never throw `exception` out while it's possible to throw a `runtime exception` – D3Hunter Apr 19 '18 at 01:55
  • Expanding what @D3Hunter said, the two bytecode versions are not equivalent! In the first, the `ArithmeticException` is rethrown after `println("Finally...")` is executed but in the second, the exception is discarded and the method returns normally. Hence, you should have also seen a difference in the output when executing these two samples. The first one would additionally print the exception and terminate with an non-zero exit status (unless a calling method catches and ignores the exception, or you set a custom uncaught exception handler). – Tim May 18 '20 at 18:02

2 Answers2

15

Inlining Finally Blocks

The question your asking has been analyzed in part at http://devblog.guidewire.com/2009/10/22/compiling-trycatchfinally-on-the-jvm/ (wayback machine web archive link)

The post will show an interesting example as well as information such as (quote):

finally blocks are implemented by inlining the finally code at all possible exits from the try or associated catch blocks, wrapping the whole thing in essentially a “catch(Throwable)” block that rethrows the exception when it finishes, and then adjusting the exception table such that the catch clauses skip over the inlined finally statements. Huh? (Small caveat: prior to the 1.6 compilers, apparently, finally statements used sub-routines instead of full-on code inlining. But we’re only concerned with 1.6 at this point, so that’s what this applies to).


The JSR instruction and Inlined Finally

There are differing opinions as to why inlining is used though I have not yet found a definitive one from an official document or source.

There are the following 3 explanations:

No offer advantages - more trouble:

Some believe that finally in-lining is used because JSR/RET did not offer major advantages such as the quote from What Java compilers use the jsr instruction, and what for?

The JSR/RET mechanism was originally used to implement finally blocks. However, they decided that the code size savings weren't worth the extra complexity and it got gradually phased out.

Problems with verification using stack map tables:

Another possible explanation has been proposed in the comments by @jeffrey-bosboom, who I quote below:

javac used to use jsr (jump subroutine) to only write finally code once, but there were some problems related to the new verification using stack map tables. I assume they went back to cloning the code just because it was the easiest thing to do.

Having to Maintain Subroutine Dirty Bits:

An interesting exchange in the comments of question What Java compilers use the jsr instruction, and what for? points that JSR and subroutines "added extra complexity from having to maintain a stack of dirty bits for the local variables".

Below the exchange:

@paj28: Would the jsr have posed such difficulties if it could only call declared "subroutines", each of which could only be entered at the start, would only be callable from one other subroutine, and could only exit via ret or abrupt completion (return or throw)? Duplicating code in finally blocks seems really ugly, especially since finally-related cleanup may often invoke nested try blocks. – supercat Jan 28 '14 at 23:18

@supercat, Most of that is already true. Subroutines can only be entered from the start, can only return from one place, and can only be called from within a single subroutine. The complexity comes from the fact that you have to maintain a stack of dirty bits for the local variables and when returning, you have to do a three-way merge. – Antimony Jan 28 '14 at 23:40

Dhaval Solanki
  • 4,589
  • 1
  • 23
  • 39
Menelaos
  • 23,508
  • 18
  • 90
  • 155
  • Not related to my question, but is it still possible to use the `JSR` and `RET` instructions, for example for *actual* subroutines (duh...)? – Clashsoft Mar 15 '15 at 17:01
  • @Clashsoft I don't think you can. According to http://stackoverflow.com/a/21150629/1688441 , `The JSR instruction is actually not even allowed in Java 7 classfiles. It is only allowed in version 49.0 or earlier classfiles, corresponding to Java 5 or earlier. In practice, it fell out of use long before that.` – Menelaos Mar 15 '15 at 17:04
  • So subroutines would simply be compiled to private methods next to the containing method? – Clashsoft Mar 15 '15 at 17:07
  • not sure... after i come back from coffee will take a look – Menelaos Mar 15 '15 at 17:28
  • 2
    "Inlining was abandoned". I am confused: isn't inlining exactly what we are getting here? The way I understand the term, when they don't use `JSR` to place the finally block "out of line" then instead the finally blocked gets *inlined* multiple times. – joeytwiddle Mar 15 '15 at 20:40
3

Compiling this:

public static void main(String... args){
    try
    {
        System.out.println("Attempting to divide by zero...");
        System.out.println(1 / 0);
    }catch(Exception e){
        System.out.println("Exception!");
    }
    finally
    {
        System.out.println("Finally...");
    }

}

And looking at result of javap -v, the finally block is simply appended at the end of every section that manages an exception (adding the catch, a finally block at line 37 is added, the one at 49 is for unchecked java.lang.Errors):

public static void main(java.lang.String...);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC, ACC_VARARGS
Code:
  stack=3, locals=3, args_size=1
     0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
     3: ldc           #3                  // String Attempting to divide by zero...
     5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     8: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
    11: iconst_1
    12: iconst_0
    13: idiv
    14: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
    17: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
    20: ldc           #6                  // String Finally...
    22: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    25: goto          59
    28: astore_1
    29: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
    32: ldc           #8                  // String Exception!
    34: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    37: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
    40: ldc           #6                  // String Finally...
    42: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    45: goto          59
    48: astore_2
    49: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
    52: ldc           #6                  // String Finally...
    54: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    57: aload_2
    58: athrow
    59: return
  Exception table:
     from    to  target type
         0    17    28   Class java/lang/Exception
         0    17    48   any
        28    37    48   any

Looks like that the original finally blocks implementation resembled what you are proposing but since Java 1.4.2 javac started inlining finally blocks, from "An Evaluation of Current Java Bytecode Decompilers"[2009] of Hamilton & Danicic:

Many of the old decompilers expect the use of subroutines for try-finally blocks but javac 1.4.2+ generates inline code instead.

A blog post from 2006 that discusses this:

The code in lines 5-12 is identical to the code in lines 19-26, which actually translates to the count++ line. The finally block is clearly copied.

uraimo
  • 19,081
  • 8
  • 48
  • 55
  • 2
    Why do you repeat code already available from the question creator? – Menelaos Mar 15 '15 at 16:43
  • 1
    It's the same code with an added catch, i put it here to be sure that it was what he meant and to show the source i compiled. If you are referring to the javap dump, it's needed and actually more clear and easy to read than what he posted. – uraimo Mar 15 '15 at 16:47