在使用简单的try/finally
块编译以下代码时,Java编译器会产生下面的输出(在ASM字节码查看器中查看):
代码:
try
{
System.out.println("Attempting to divide by zero...");
System.out.println(1 / 0);
}
finally
{
System.out.println("Finally...");
}
字节码:
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
我在加入catch
块后注意到编译器将finally
块复制了3次(没有再次发布字节码)。这似乎在类文件中浪费空间。此复制也似乎不限于最大指令数(类似于内联的方式),因为即使在添加更多对System.out.println
的调用时,它仍会复制finally
块。
然而,我的自定义编译器使用不同的编译方法来编译相同的代码,其执行结果完全相同,但使用GOTO
指令需要更少的空间:
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
为什么Java编译器(或Eclipse编译器)会多次复制finally
块的字节码,甚至在使用athrow
重新抛出异常时,当可以使用goto
实现相同语义时?这是优化过程的一部分还是我的编译器做错了?
(在两种情况下的输出结果都是...)
Attempting to divide by zero...
Finally...
javac
曾经使用jsr
(跳转子例程) 只需编写一次finally
代码,但与使用堆栈映射表的新验证相关的一些问题。我猜他们之后又回归到克隆代码,因为这是最简单的做法。 - Jeffrey Bosboom