为什么 Java 编译器会添加一个额外的 try-catch 块?

逆向工程 爪哇 字节码
2021-06-21 03:59:04

假设我们使用 Java 6+ 编译器编译此方法:

void test(int x) {
    try {
        x += 777;
    } finally {
        x -= 333;
    }
}

反汇编后的代码如下所示:

test(I)V
    TRYCATCHBLOCK L0 L1 L2 null
    TRYCATCHBLOCK L2 L3 L2 null
   L0              // Start of try block
    IINC 1 777     // The body of try block
   L1              // Start of finally block in case of successful execution 
    IINC 1 -333    // Body of finally block
    GOTO L4        // End of 'success' finally block 
   L2              // Start of finally block in case of exception
    ASTORE 2       // Remember exception
   L3
    IINC 1 -333    // Duplicated body of finally block
    ALOAD 2        // Load remembered exception
    ATHROW         // Rethrow the exception 
   L4              // End of try/finally  
    RETURN
   L5

显然,这个声明指定了“try”部分和“finally”块的开头:

    TRYCATCHBLOCK L0 L1 L2 null

但是下一个声明看起来很奇怪:

    TRYCATCHBLOCK L2 L3 L2 null

为什么Java编译器在记住异常的同时保护它,为什么它递归地将自己指定为处理程序(即两个L2s)?

2个回答

一个推测。正如我们所看到的,没有实际的代码重复,多个 trycatch 块在失败与否的不同情况下充当 goto 标签(因为还存在 finally 子句)。如果您按照程序流程进行操作,您会看到没有发生重复。

我只是使用 java 8 编译器反汇编了它,并且不再生成第二个 trycatchblock

test(I)V
   TRYCATCHBLOCK L0 L1 L2 null
  L0
   IINC 1 777
  L1
   IINC 1 -333
  L3
   GOTO L4
  L2
   FRAME SAME1 java/lang/Throwable
   ASTORE 2
   IINC 1 -333
   ALOAD 2
   ATHROW
  L4
   FRAME SAME
   RETURN
  L5

我的猜测是这是一些被 JVM 忽略的遗留处理