《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 liangfei.me 「liangfei」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

最近一直在看Java虚拟机规范,发现直接分析bytecode更能加深对Java语言的理解。

之前看过一篇关于 returnfinally 执行顺序的文章,仅在 Java 的语言层面做了分析,其实我倒觉得直接看 bytecode 可能来的更清晰一点。

先看一个只有 try-finally,没有 catch 的例子。

try - finally

public class ExceptionTest {
public void tryFinally() {
try {
tryItOut();
} finally {
wrapItUp();
}
}


// auxiliary methods
public void tryItOut() { }

public void wrapItUp() {}
}

通过 javap -c ExceptionTest 来查看它的字节码。

public void tryFinally();
Code:
0: aload_0
1: invokevirtual #2 // Method tryItOut:()V
4: aload_0
5: invokevirtual #3 // Method wrapItUp:()V
8: goto 18
11: astore_1
12: aload_0
13: invokevirtual #3 // Method wrapItUp:()V
16: aload_1
17: athrow
18: return
Exception table:
from to target type
0 4 11 any

如果没有抛出异常,那么它的执行顺序为

0: aload_0
1: invokevirtual #2 // Method tryItOut:()V
4: aload_0
5: invokevirtual #3 // Method wrapItUp:()V
18: return

如果抛出了异常,JVM 会在

Exception table:
from to target type
0 4 11 any

中进行控制跳转。如果是位于0到4字节之间的命令抛出了任何类型(any type)的异常,会跳转到11字节处继续运行。

11: astore_1
12: aload_0
13: invokevirtual #3
16: aload_1
17: athrow

astore_1会把抛出的异常对象保存到local variable数组的第二个元素。下面两行指令用来调用成员方法wrapItUp。

12: aload_0
13: invokevirtual #3

最后通过

16: aload_1
17: athrow

重新抛出异常。

通过以上分析可以得出结论:

在try-finally中,try块中抛出的异常会首先保存在local variable中,然后执行finally块,执行完毕后重新抛出异常。

如果我们把代码修改一下,在try块中直接return。

try - return - finally

public void tryFinally() {
try {
tryItOut();
return;
} finally {
wrapItUp();
}
}

”反汇编“一下:

 0: aload_0
1: invokevirtual #2 // Method tryItOut:()V
4: aload_0
5: invokevirtual #3 // Method wrapItUp:()V
8: return
9: astore_1
10: aload_0
11: invokevirtual #3 // Method wrapItUp:()V
14: aload_1
15: athrow

可以看出finally块的代码仍然被放到了return之前。

如果try块中有return statement,一定是finally中的代码先执行,然后return。

JVM规范是这么说的:

Compilation of a try-finally statement is similar to that of try-catch. Pior to transferring control outside thetry statement, whether that transfer is normal or abrupt, because an exception has been thrown, thefinally clause must first be execute. try - catch - finally

给上面的代码加一个catch块

public void tryCatchFinally() {
try {
tryItOut();
} catch (TestExc e) {
handleExc(e);
} finally {
wrapItUp();
}
}

javap一下

public void tryCatchFinally();
Code:
0: aload_0
1: invokevirtual #2
4: aload_0
5: invokevirtual #3
8: goto 31
11: astore_1
12: aload_0
13: aload_1
14: invokevirtual #5
17: aload_0
18: invokevirtual #3
21: goto 31
24: astore_2
25: aload_0
26: invokevirtual #3
29: aload_2
30: athrow
31: return
Exception table:
from to target type
0 4 11 Class TestExc
0 4 24 any
11 17 24 any

通过Exception table可以看出:

  • catch监听 0 ~ 4 字节类型为TextExc的异常。
  • finally为 0 ~ 4 以及 11 ~ 17 字节任何类型的异常。

也就说 catch block 本身也在 finally block 的管辖范围之内。

如果catch block 中有 return statement,那么也一定是在 finally block 之后执行。

文章目录
  1. 1. try - finally
  2. 2. try - return - finally