gpt4 book ai didi

java - 为什么 Java 编译器复制 finally block ?

转载 作者:IT老高 更新时间:2023-10-28 20:47:42 28 4
gpt4 key购买 nike

当使用简单的 try/finally block 编译以下代码时,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 block 时,我注意到编译器将 finally block 复制了 3 次(不再发布字节码) .这似乎浪费了类文件中的空间。复制似乎也不限于最大数量的指令(类似于内联的工作方式),因为当我添加更多对 System.out 的调用时,它甚至复制了 finally block .println.


但是,我的自定义编译器使用不同方法编译相同代码的结果在执行时完全相同,但使用 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 block 的字节码,甚至使用athrow重新抛出异常,而同样的语义可以可以使用 goto 实现吗?这是优化过程的一部分,还是我的编译器做错了?


(两种情况下的输出都是...)

Attempting to divide by zero...
Finally...

最佳答案

内联 finally block

您提出的问题已在 http://devblog.guidewire.com/2009/10/22/compiling-trycatchfinally-on-the-jvm/ 进行了部分分析。 (回程机网页存档链接)

该帖子将显示一个有趣的示例以及诸如(引用)之类的信息:

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).


JSR指令和Inlined finally

虽然我还没有从官方文档或来源中找到明确的说法,但对于为什么使用内联存在不同的意见。

有以下3种解释:

没有优惠优势 - 更麻烦:

有些人认为使用 finally 内联是因为 JSR/RET 没有提供主要优势,例如来自 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.

使用堆栈映射表进行验证的问题:

@jeffrey-bosboom 在评论中提出了另一种可能的解释,我在下面引用他:

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.

必须维护子程序脏位:

问题评论中的有趣交流What Java compilers use the jsr instruction, and what for?指出 JSR 和子例程“由于必须为局部变量维护一堆脏位而增加了额外的复杂性”。

交易所下方:

@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

关于java - 为什么 Java 编译器复制 finally block ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29061627/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com