场景
对于以下代码:
public int test() {
int x;
try {
x = 1;
return x;
} catch (Exception e) {
x = 2;
return x;
} finally {
x = 3;
}
} 结论
-
如果try语句没有出现属于Exception或其子类的异常,返回值为1
-
如果出现,返回值为2
-
如果出现Exception以外的其它异常,则没有返回,方法异常退出
解释
通过javap获取的字节码如下(主体部分):
public int test();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=1, locals=5, args_size=1
0: iconst_1 //将int类型常量1压入操作数栈-> 操作数栈:1,局部变量表:空
1: istore_1 //将int类型值出栈,存入局部变量1-> 操作数栈:空,局部变量表:slot1=1,也就是x=1
2: iload_1 //从局部变量表1中装载int类型值,压入操作数栈-> 操作数栈:1,局部变量表:slot1=1
3: istore_2 //将int类型值出栈,存入局部变量2-> 操作数栈:空,局部变量表:slot1=1,slot2=1
4: iconst_3 //将int类型常量3压入操作数栈-> 操作数栈:3,局部变量表:slot1=1,slot2=1
5: istore_1 //将int类型值出栈,存入局部变量1-> 操作数栈:空,局部变量表:slot1=3,slot2=1
6: iload_2 //从局部变量表2中装载int类型值,压入操作数栈-> 操作数栈:1,局部变量表:slot1=3,slot2=1
7: ireturn //返回int类型,此时操作数栈顶为1,所以返回1(无异常情况)
8: astore_2 //当0到3行出现异常跳至这里,将catch中的Exception e复制,存入局部变量表2->slot2 = e
9: iconst_2 //将int类型常量2压入操作数栈-> 操作数栈:2,局部变量表:slot2=e
10: istore_1 //将int类型值出栈,存入局部变量表1-> 操作数栈:空,局部变量表:slot1=2
11: iload_1 //从局部变量表1中装载int类型值,压入操作数栈-> 操作数栈:2,局部变量表:slot1=2
12: istore_3 //将int类型值出栈,存入局部变量表3-> 操作数栈:空,局部变量表:slot1=2,slot3=2
13: iconst_3 //将int类型常量3压入操作数栈-> 操作数栈:3,局部变量表:slot1=2,slot3=2
14: istore_1 //将int类型值出栈,存入局部变量表1-> 操作数栈:空,局部变量表:slot1=3,slot3=2
15: iload_3 //从局部变量表3中装载int类型值,压入操作数栈-> 操作数栈:2,局部变量表:slot1=3,slot3=2
16: ireturn //返回int类型,此时操作数栈顶为2,所以返回2(Exception 情况)
17: astore 4 //不属于Exception及其子类的异常存入局部变量表4-> 局部变量表:slot4=异常引用
19: iconst_3 //将int类型常量3压入操作数栈-> 操作数栈:3,局部变量表:slot4=异常引用
20: istore_1 //将int类型值出栈,存入局部变量表1-> 操作数栈:空,局部变量表:slot1=3,slot4=异常引用
21: aload 4 //将局部变量表4中的异常引用压入栈顶
23: athrow //抛出栈顶异常
Exception table:
from to target type
0 4 8 Class java/lang/Exception
0 4 17 any
8 13 17 any
17 19 17 any
上述Code中每行字节码都添加了注释,分析了操作数栈和局部变量表的状态。
其中,字节码行号0到7就是没有异常时的字节码流,返回值为1。
需要注意的是,上述Code中,第4、5行即为finally中的:x=3。编译器自动在每段可能的分支路径之后都将finally语句块的内容冗余生成一遍来实现finally语义。同样的还有第13、14行,第19、20行。
然后我们来看异常处理表:
Exception table:
from to target type
0 4 8 Class java/lang/Exception
0 4 17 any
8 13 17 any
17 19 17 any
其表示的意思是,在字节码行号中:
-
0到3行如果发现Exception或其子类异常,则跳到第8行处理;如果发现其它异常,则跳到第17行处理
-
8到12行,则跳到17行处理
-
异常表中的第四行:17 19 17 any (这里还不理解什么意思,翻阅了虚拟机规范关于异常表的说明,只说了to(也就是end_pc,其排他性是设计上的历史错误,这里from和target相同的问题等查到相关资料再作补充,可能是虚拟机为了应对某种情况特殊处理的,不行还是得翻阅源码~)
结合这个异常表和Code中的注释,可以发现,如果try语句中发生了Exception及其子类异常,那么执行的字节码为第8-16行,最终返回值为2。其他异常的话,则跳到第17行处理,执行第17-23行,最终将异常抛出,方法值没有返回。
从异常表中还可以发现另一问题,在catch块中如果出现了异常(第8到12行),那么也会跳到第17行进行处理,也就是执行finally代码块。
注:对于x=1;这条语句,虽然在字节码中体现为iconst和istore两条字节码,但是他们依然是原子操作,原子操作不是说只有一条指令,而是不可中断的一个或一系列操作。可以对比一下long和double的非原子协议。
参考:《深入理解Java虚拟机》






还没有评论,来说两句吧...