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

  Print this page

1.finally子句和try子句中return的先后关系 Copy to clipboard
Posted by: zcjl
Posted on: 2005-09-05 23:49

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

下面是我从《深入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指令)。

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

2.Re:finally子句和try子句中return的先后关系 [Re: zcjl] Copy to clipboard
Posted by: wmgreat
Posted on: 2005-09-06 20:39

在Practical Java中第23条专门说明这一点,return语句如果在try中的话,当执行完return语句后执行权跳转到finally中,这里也是经常会出问题的地方。可以比较下面两端程序:
public class Test{
public int b;
public static void main(String[] args){
Test test = new Test();
System.out.println(test.tt());
}

public int tt(){
b = 23;
try{
return b= 88;
}
finally {
b = 30;
}
}
}

public class Test{
public int b;
public static void main(String[] args){
Test test = new Test();
System.out.println(test.tt());
}

public int tt(){
b = 23;
try{
return b= 88;
}
finally {
return b = 30;
}
}
}


   Powered by Jute Powerful Forum® Version Jute 1.5.6 Ent
Copyright © 2002-2021 Cjsdn Team. All Righits Reserved. 闽ICP备05005120号-1
客服电话 18559299278    客服信箱 714923@qq.com    客服QQ 714923