下面是摘自周志明所著《深入理解Java虚拟机》的内容。
下面代码是一段演示异常表如何运作的例子,这段代码主要演示了在字节码层面中try-catch-finally是如何实现的。 在阅读字节码之前,大家不妨先看看下面的Java源码,想一下这段代码的返回值在出现异常和不出现异常的情况下分别应该是多少?
//Java源码 public int inc() {
int x;
try {
x = 1;
return x;
} catch (Exception e) {
x = 2;
return x;
} finally {
x = 3;
}
}
//编译后的ByteCode字节码及异常表public int inc();
Code:
Stack=1,Locals=5,Args_size=1
0:iconst_1//try块中的x=11:istore_1
2:iload_1//保存x到returnValue中,此时x=13:istore 4
5:iconst_3//finaly块中的x=36:istore_1
7:iload 4//将returnValue中的值放到栈顶,准备给ireturn返回9:ireturn
10:astore_2//给catch中定义的Exception e赋值,存储在Slot 2中11:iconst_2//catch块中的x=212:istore_1
13:iload_1//保存x到returnValue中,此时x=214:istore 4
16:iconst_3//finaly块中的x=317:istore_1
18:iload 4//将returnValue中的值放到栈顶,准备给ireturn返回20:ireturn
21:astore_3//如果出现了不属于java.lang.Exception及其子类的异常才会走到这里22:iconst_3//finaly块中的x=323:istore_1
24:aload_3//将异常放置到栈顶,并抛出25:athrow
Exception table:
from to target type
0 5 10 Class java/lang/Exception
0 5 21 any
10 16 21 any
编译器为这段Java源码生成了3条异常表记录,对应3条可能出现的代码执行路径。 从Java代码的语义上讲,这3条执行路径分别为:
如果try语句块中出现属于Exception或其子类的异常,则转到catch语句块处理。
如果try语句块中出现不属于Exception或其子类的异常,则转到finally语句块处理。
如果catch语句块中出现任何异常,则转到finally语句块处理。
返回到我们上面提出的问题,这段代码的返回值应该是多少?对Java语言熟悉的读者应该很容易说出答案:如果没有出现异常,返回值是1;如果出现了Exception异常,返回值是2;如果出现了Exception以外的异常,方法非正常退出,没有返回值。
我们一起来分析一下字节码的执行过程,从字节码的层面上看看为何会有这样的返回结果。字节码中第0~4行所做的操作就是将整数1赋值给变量x,并且将此时x的值复制一份副本到最后一个本地变量表的Slot中(这个Slot里面的值在ireturn指令执行前将会被重新读到操作栈顶,作为方法返回值使用。 为了讲解方便,笔者给这个Slot起了个名字:returnValue)。如果这时没有出现异常,则会继续走到第5~9行,将变量x赋值为3,然后将之前保存在returnValue中的整数1读入到操作栈顶,最后ireturn指令会以int形式返回操作栈顶中的值,方法结束。 如果出现了异常,PC寄存器指针转到第10行,第10~20行所做的事情是将2赋值给变量x,然后将变量x此时的值赋给returnValue,最后再将变量x的值改为3。 方法返回前同样将returnValue中保留的整数2读到了操作栈顶。 从第21行开始的代码,作用是变量x的值赋为3,并将栈顶的异常抛出,方法结束。
尽管大家都知道这段代码出现异常的概率非常小,但并不影响它为我们演示异常表的作用。