前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >try catch finally 实现机制

try catch finally 实现机制

原创
作者头像
Erossssssss
修改2021-04-09 18:11:29
2.3K0
修改2021-04-09 18:11:29
举报
文章被收录于专栏:Eleanor的专栏Eleanor的专栏

try catch finally如何执行异常时跳转?finally 语句为何一定会执行?

其背后的原因值得深究,我们从JVM的角度看看try catch finally这个语法糖背后的实现原理。

JVM 如何执行 try - catch

创建一个TryCatchFinallyDemo.java 类,在foo()方法中声明了try-catch block;声明了 handleException 这个空方法。

代码示例
代码示例

使用 javac 指令将其编译为class 文件,并使用javap -c -v -s 查看结果。相对于没有try-catch block 的代码,下列代码中多出了一个Exception Table。

编译后的指令
编译后的指令

在编译后字节码中,每个方法都附带一个异常表(Exception table),异常表里的每一行表示一个异常处理器,由 from 指针、to 指针、target 指针、所捕获的异常类型 type 组成。

这些指针的值是字节码索引,用于定位字节码 其含义是在[from, to)字节码范围内,抛出了异常类型为type的异常,就会跳转到target表示的字节码处。

比如,上面的例子异常表表示:在0到3中间(不包含 3)如果抛出了Exception异常,就跳转到6执行。

多个catch 语句

下面举一个有多个catch 语句的例子,虽然下面三个异常不会发生。

使用javac -s 可以简单看到对应的ctach 块字节码。Exception Table 中变为三种类型的异常,如果[0,3)的代码段(不包括3)发生异常,则可以跳转到,6,15,24行代码寻找可捕获的异常类型。

当程序出现异常时,Java 虚拟机会从上至下遍历异常表中所有的条目。当触发异常的字节码索引值在某个异常条目的[from, to)范围内,则会判断抛出的异常与该条目想捕获的异常是否匹配。

  • 如果匹配,Java 虚拟机会将控制流跳转到 target 指向的字节码;如果不匹配则继续遍历异常表
  • 如果遍历完所有的异常表,还未匹配到异常处理器,那么该异常将蔓延到调用方(caller)中重复上述的操作。最坏的情况下虚拟机需要遍历该线程 Java 栈上所有方法的异常表。 如果在方法栈中所有的调用方中,都未找到可匹配的异常表,JVM会清空当前方法栈。

多个异常示例
多个异常示例

finally 分析

finally 始终执行的秘密

那么,JVM如何保证finally 关键字始终执行呢?我们为上述例子增加一个finally block。

finally 代码示例
finally 代码示例

重新编译后使用javap -c 查看。

编译后的finally命令段
编译后的finally命令段

可以看到,字节码中包含了三份 finally 语句块,都在程序正常 return 和异常 throw 之前。其中两处在 try 和 catch 调用 return 之前,一处是在异常 throw 之前。

Java 采用方式是复制 finally 代码块的内容,分别放在 try catch 代码块所有正常 return 和 异常 throw 之前。所以finally 代码块始终会执行。

下面有两个并不常用的场景。

finally 修改返回值场景

下列代码运行结果是1, 还是3呢?这个场景能迷惑到不少人。我们执行一边,结果是1。

修改返回值的示例
修改返回值的示例

编译查看字节码:

修改返回值的示例字节码
修改返回值的示例字节码

通过字节码,我们发现,在try语句的return块中,return 返回的变量并不是直接返回 i 值,而是在执行finally块之前把i值存储在临时区域,当执行return时直接返回的临时区域中的值,即使在finally语句中把变量 i 的值修改了,也不会影响返回的值。

finally 中有return 的场景

当finally 中有return 语句时,return 语句会重写 try-block, catch-block的返回值。

略修改上个章节的例子:在finally 语句中增加一行返回值操作。运行结果却变成了3, 返回了finally block 中的值。

finally中有return的场景
finally中有return的场景

编译后查看字节码,并与上个章节的例子做对比。左侧是上个章节编译后的字节码,右侧是上面例子编译的字节码。

每个try block, catch block 后侧,return指令之前,都会拷贝finally block的代码块。可以看到,虽然try-catch block中的i值被暂存了,但是由于finally 有return 语句,返回的依然是finally 修改后的i值。

finally中有return的场景编译后的指令
finally中有return的场景编译后的指令

总结

  • 第一,JVM 采用异常表的方式来处理 try-catch 的跳转逻辑;
  • 第二,finally 的实现采用拷贝 finally 语句块的方式来实现 finally 一定会执行的语义逻辑;
  • 第三,讲解了在 finally 中有 return 语句或者 抛异常的情况。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • JVM 如何执行 try - catch
    • 多个catch 语句
    • finally 分析
      • finally 始终执行的秘密
        • finally 修改返回值场景
          • finally 中有return 的场景
          • 总结
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档