假设我们使用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编译器在记住异常时会保护该异常,为什么它会递归地将自身指定为处理程序(即两个L2)?

评论

如果使用catch而不是finally会发生同样的事情吗?

@Devolus编号。它添加了单个TRYCATCHBLOCK L0 L1 L2 java / lang / Exception

几年前在SO上也有同样的问题:stackoverflow.com/questions/6386917/…

我同意Devolus。我从未真正在生产代码中看到try {} finally {}。

丹尼尔,不是同一个问题。 “从几年前开始”的问题明确指出,即使有挡木块,他的案子也会发生。

#1 楼

一个猜测。我们可以看到没有实际的代码重复,多个trycatch块在失败或失败的不同情况下都充当goto标签(因为还存在finally子句)。如果遵循程序流程,您会发现没有重复发生。

#2 楼

我只是使用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忽略的一些遗留处理方法