JVM 如何执行 Try catch finally 块



根据 Java 语言规范第 §14.20.2 节

带有 finally 块的 try 语句通过首先执行 try 块来执行。然后有一个选择:

  • 如果 try 块的执行正常完成,则最终 块被执行,然后有一个选择:
    • 如果 finally 块正常完成,则 try 语句正常完成。
    • 如果 finally 块由于原因 S 而突然完成,则 try 语句由于原因 S 而突然完成

如果我正确解释它,那么在执行 try 块后最终被调用,但是这一切是如何工作的以及为什么我得到输出,

public static int TestTryFinallyBlock()  
{
    int  i =0;
    try 
    {
        i= 10; //Perform some more operation
        return i;
    }       
    finally
    {
        i = 40; 
    }
}
public static void main( String[] args )
{
    int i1 = TestTryFinallyBlock(); //Here the output was 10 not 40
}   

我想知道这个东西是如何产生输出 10 的。

是当执行try块并遇到返回语句时,输出值已经被推送到堆栈,然后执行最终块

我知道首先遇到返回,然后最后块运行,因此输出为 10,但是jvm 如何解释或 try final 块如何由 jvm 处理或转换?
jvm 是使用 GOTO 部分跳转部分转到最终部分还是堆栈已经维护?

经过一番搜索并查看生成的字节码,我发现实际上没有看起来的最终块,也没有JVM生成的跳转或goto语句。
上面的代码被翻译为(如果我正确解释字节码,如果我错了,请纠正我)

public static int TestTryFinallyBlock()  
{
  int returnValue; //A temporary return variable
  try
  {
     int  i = 0;     
     i = 10; 
     returnValue = i; 
     i = 40; 
     return returnValue;    
  }
  catch (RuntimeException e)
  {
       i = 40; //finally section code id copied here too
       throw e;
  }
}

注意:如果'i'是对可变类对象的引用,并且对象的内容在 finally 块中发生了更改,那么这些更改也会反映在返回值中。

编译 最后

try-finally 语句的编译类似于 try-catch 的编译。在将控制权转移到 try 语句之外之前,无论这种转移是正常的还是突然的,因为抛出了异常,都必须首先执行 finally 子句。对于这个简单的例子:

void tryFinally() {
    try {
        tryItOut();
    } finally {
        wrapItUp();
    }
}

编译后的代码是:

Method void tryFinally()
0   aload_0             // Beginning of try block
1   invokevirtual #6    // Method Example.tryItOut()V
4   jsr 14              // Call finally block
7   return              // End of try block
8   astore_1            // Beginning of handler for any throw
9   jsr 14              // Call finally block
12  aload_1             // Push thrown value
13  athrow              // ...and rethrow value to the invoker
14  astore_2            // Beginning of finally block
15  aload_0             // Push this
16  invokevirtual #5    // Method Example.wrapItUp()V
19  ret 2               // Return from finally block
Exception table:
From    To      Target      Type
0       4       8           any

控制可以通过四种方式传递到 try 语句之外:从该块的底部掉下来、返回、执行中断或继续语句或引发异常。

要了解有关javac如何解释最终块的更多信息。请参考 JLS - 3.13。编译 final

输入 return 时,该方法已准备好返回 10。 10 作为返回值在堆栈上。最后块被执行并将i设置为 40,但该i与返回值的位置不同。现在,如果有副作用,例如:

public static int TestTryFinallyBlock()  
{
    int  i =0;
    try 
    {
        i= 10; //Perform some more operation
        return i;
    }       
    finally
    {
        i = 40; 
        System.out.println("local: "+i);
    }
}

打印 40 个。

这是因为 return 语句函数的方式以及它与 try with finally 语句交互的方式。

JLS 的第 §14.17 节描述了返回语句。

带有表达式的 return 语句尝试将控制权转移给包含它的方法的调用者;表达式的值将成为方法调用的值。更准确地说,执行这样的 return 语句首先计算表达式。如果表达式的计算由于某种原因突然完成,则返回语句会因此而突然完成。如果表达式的计算正常完成,生成值 V,则返回语句突然完成,原因是值为 V 的返回。

最后一句表示如果 return 语句的表达式正常计算,则 return 语句会突然完成。 在您的示例中,由于 return 语句和原因是return值为 10(i计算为值 10)的原因,try块突然终止。

由于在您的示例中,return正在尝试使用 finally 块,因此 JLS 的第 §14.20.2 节告诉我们接下来会发生什么:

  • 如果 try 块的执行由于任何其他原因突然完成 R,然后执行最终块,然后有一个选择:
    • 如果 finally 块正常完成,则 try 语句由于原因 R 而突然完成。
    • 如果 finally 块由于原因 S 而突然完成
    • ,则 try 语句由于原因 S 而突然完成(并且原因 R 被丢弃)。

因此,由于 try 块由于 return 语句被计算为值 10 而突然终止,并且由于 finally 块正常完成,因此该方法返回 10。

现在 i 的值是 40,但你没有得到 40,因为你在使其成为 40 之前得到了( returned )这个值。

就像

i=10
return i
i=40
if return here,you get 40

虽然不是一个好的做法,只是为了演示。

public static int TestTryFinallyBlock()  
    {
        int  i =0;
        try 
        {
            i= 10; //Perform some more operation
        }       
        finally
        {
            i = 40;
            return i;
        }
    }

现在,您将在 i 中得到 40 的值。

*

作为旁注:*永远不要编写资源清理以外的任何业务逻辑。这会扼杀可读性并导致错误。

最新更新