finally子句和try子句中return的先后关系

finally子句和try子句中return的先后关系

这个问题起因于java区基础版块的一篇帖子:
<a href="http://community.csdn.net/Expert/topic/3636/3636856.xml?temp=.9524347" target="_blank">http://community.csdn.net/Expert/topic/3636/3636856.xml?temp=.9524347</a>


下面是我从《深入Java虚拟机 2E》(中文版,曹晓钢 蒋靖译)中得到的解释
由于现学现卖,不免有大量摘抄的地方,望诸位谅解。

jsr指令是使java虚拟机跳转到微型子例程[注释1]的操作码,另外一条指令使jsr_w,后者支持比前者更长的操作数(4个字节长)。当java虚拟机遇到jsr或是jsr_w指令,它会把返回地址压入栈,然后从微型子例程的开始处继续执行。

微型子例程执行完毕后(这里指的是finally子句中最后一条语句正常执行完毕,不包括抛出异常,或执行return、continue、break等情况),将调用ret指令,ret指令的功能是执行从子例程中返回的操作。

你也许会认为,ret指令应当从栈中弹出返回地址,因为返回地址也已被jsr指令压入栈。不是这样的,ret指令并不会这样做。在每一个子例程的开始处,返回地址都从栈顶端弹出,并且存储在局部变量中,稍后,ret指令将会从这个局部变量中取出返回地址。这种对返回地址的不对称的工作方式是必要的,因为finally子句本身会抛出异常或者含有return、break、continue等语句。由于这些可能性的存在,这个被jsr指令压入栈的额外返回地址必须立即从栈中移除。因此,当finally子句通过break、continue、return或者抛出异常退出时,这个问题就不必再考虑了。

先看主帖里的示例代码(为了bytecode的清晰,去掉了无关的打印语句和捕获异常语句)
// Test1.java
public class Test1{
    public static void main(String[] args){
        System.out.print(tt());
    }

    public static int tt(){
        int b = 23;
        try{
            return b = 88;
        }
        finally {
            if(b > 25){
                System.out.println("b > 25 : "+b);
            }
        }
    }
}

//调用javap -c Test1后得到的字节码序列
Compiled from "Test1.java"
public class Test1 extends java.lang.Object{
public Test1();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:   invokestatic    #3; //Method tt:()I
   6:   invokevirtual   #4; //Method java/io/PrintStream.print:(I)V
   9:   return

public static int tt();
  Code:
   0:   bipush  23      // 将数据23转换为int类型,然后将其压入栈
   2:   istore_0        // 从栈中弹出int类型值,然后将其存到位置为0的局部变量中
                        // 执行int b = 23;

   3:   bipush  88      // 将数据88转换为int类型,然后将其压入栈
   5:   dup             // 复制栈顶部的一个字,然后再将复制内容压入栈
                        // 这里是执行b = 88语句

   6:   istore_0        // 从栈中弹出int类型值,然后将其存到位置为0的局部变量中
                        // 这里存的是88,b = 88语句的执行后b的值

   7:   istore_1        // 从栈中弹出int类型值,然后将其存到位置为1的局部变量中
                        // 这里存的是88,b = 88语句的执行结果,将要被return语句返回的值

   8:   jsr     19      // 把返回地址压入栈,跳转至偏移量指定位置处执行分支操作
                        // 这里先将指令8的偏移地址压入栈,然后跳转到指令19,finally子句的开始

   11:  iload_1         // 将位置为1的int类型局部变量压入栈
                        // 将先前存到位置为1的局部变量中的返回值压入栈

   12:  ireturn         // 从方法中返回int类型的数据
                        // 即try子句中最后的一步操作:return方法tt的的返回值

   13:  astore_2
   14:  jsr     19
   17:  aload_2
   18:  athrow
                        // 指令13-18是针对try子句产生异常的情况,这里不做分析

   19:  astore_3        // 从栈中弹出对象引用,然后将其存到位置为3的局部变量中
                        // finally子句的开始
                        // 这里弹出的是指令8(不发生异常的情况下)中压入栈的返回地址,原因在前面摘抄的文字中已经解释过了


   20:  iload_0         // 将位置为0的int类型局部变量压入栈
   21:  bipush  25      // 将数据25转换为int类型,然后将其压入栈
   23:  if_icmple       51
   26:  getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   29:  new             #5; //class StringBuffer
   32:  dup
   33:  invokespecial   #6; //Method java/lang/StringBuffer."<init>":()V
   36:  ldc             #7; //String b > 25 :
   38:  invokevirtual   #8; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
   41:  iload_0
   42:  invokevirtual   #9; //Method java/lang/StringBuffer.append:(I)Ljava/lang/StringBuffer;
   45:  invokevirtual   #10; //Method java/lang/StringBuffer.toString:()Ljava/lang/String;
   48:  invokevirtual   #11; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
                        // 指令20-48执行if子句,略过,不做分析

   51:  ret     3       // 从子例程中返回到保存在局部变量3中的地址
                        // 呼应指令19,从finally子句转回到try子句
  Exception table:
   from   to  target type
     3    11    13   any
    13    17    13   any

}
------------------------------------------------------------------
上面的中文注释,直接对应在指令后面的是《深入java虚拟机》中的指令说明,其下的才是我所理解的行为。
可以看出,源代码中简单的一句return b = 88;语句,编译成bytecode后,对应了3-12条指令。其中指令8是一个分界点,指令3-7执行b = 88这个语句,且赋值运算后将b的值和方法的返回值分别存入到位置为0和1的局部变量中,指令11、12则从位置为1的局部变量中取出方法的返回值,并返回。指令8则是将自己的偏移地址压入栈,然后跳转到指令19,开始执行finally子句。
finally子句先将指令8的偏移地址弹出栈,并保存到位置为3的局部变量中,然后开始执行后面的语句。当后面的语句执行完毕,通过指令51,从位置为3的局部变量中取出指令8的偏移地址,然后返回执行指令8的后续指令(try子句中最终的return指令)。
(待续,明天再补上对finally子句中直接用写return 123;的理解)

注释:
1.字节码中的finally子句在方法内部的表现很像“微型子例程”,因此本文中的“微型子例程”特指finally子句。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值