zl程序教程

您现在的位置是:首页 >  其他

当前栏目

通过字节码理解try-catch-finally

2023-03-20 14:53:45 时间

通过字节码理解try-catch-finally

场景

对于以下代码:

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获取的字节码如下(主体部分):

此代码由Java架构师必看网-架构君整理
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虚拟机》