Why does the Java Compiler copy finally blocks?

五迷三道 提交于 2019-12-02 22:19:00
Menelaos Bakopoulos

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

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!