Java try-finally中的return语句

预备知识

这里只提到部分涉及到的知识点,详情查看

  1. JVM Spec: Run-Time Data Areas
  2. JVM Spec: Exceptions and finally
  3. JVM Spec: Instructions

JVM栈

在每个JVM线程被创建的时候,与之对应的私有JVM栈也会被创建。

JVM栈存储栈帧。类似于C语言中的栈,JVM栈中包含局部变量和部分的结果,同时在方法调用和返回中起到一定的作用。

栈帧

每一时刻在一个线程中,只有一个正在执行的方法的栈帧是激活的。这个栈帧我们叫做“当前栈帧”,对应的方法叫做“当前方法”。对局部变量和操作数栈的操作一般都与当前栈帧有关。

当一个方法被调用,控制转移到新的方法时,一个新的栈帧会被创建并成为当前栈帧。

当一个方法返回时,当前栈帧将其方法调用的结果返回到之前的栈帧。当前栈帧随后被废弃,之前的栈帧成为当前栈帧。

局部变量

通过索引对局部变量进行寻址。第一个局部变量的索引是0。

操作数栈

每个栈帧都有一个LIFO的栈,叫做操作数栈。

栈帧刚创建时,其中的操作数栈是空的。JVM提供了将常量、局部变量或是字段的值推入操作数栈的指令。其他JVM指令则将操作数从操作数栈上取下,对它们进行操作,然后将结果推回操作数栈。操作数栈也被用于准备传递给方法的参数或是接受方法的结果。

异常与finally

生成50.0或更低版本class文件的Java编译器可能会通过异常处理机制和两种特殊的指令: jsr(“jump to subrutine”) 和 ret(“return from subroutine”)来实现try-finally结构。

当子程序执行jsr指令时,它会把自己的返回地址,也就是jsr之后将被执行的命令的地址作为returnAddress类型的数据值推入操作数栈。子程序会把返回值存储在一个局部变量中。在子程序的最后,ret指令会从局部变量中取回返回地址,然后将控制转移到指定返回地址的那条指令上。

finally语句在JVM代码中会编译成它所在方法的一个子程序。

控制可以通过几种不同的方式传递给finally子句。如果try语句正常完成,finally子程序会在执行下一条表达式之前通过jsr命令被执行。try语句中的break或continue语句会在将控制转移到try语句外之前先执行jsr指令跳转到finally语句并执行。如果try语句中执行了return,编译后的代码会这样做:

  1. 如果有返回值则将其存储在一个局部变量中。
  2. 执行jsr指令跳转到finally语句。
  3. 从finally语句中向上返回,返回值从之前保存的局部变量中取。

编译器会准备一个特殊的异常处理器来捕获try语句中抛出的任何异常。如果执行try语句时有异常抛出,这个异常处理器或这样做:

  1. 将异常保存在一个局部变量中。
  2. 执行jsr指令跳转到finally语句。
  3. 从finally语句中向上返回,重新抛出异常。

一个实例

1
2
3
4
5
6
7
8
static int test() {
int x = 1;
try {
return x;
} finally {
++x;
}
}

这个方法的返回值是多少?

前面我们看了JVM的规范,这里我们通过查看字节码的方式来研究一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static int test();
Code:
0: iconst_1
1: istore_0
2: iload_0
3: istore_1
4: iinc 0, 1
7: iload_1
8: ireturn
9: astore_2
10: iinc 0, 1
13: aload_2
14: athrow
Exception table:
from to target type
2 4 9 any

下面我们画图来观察一下这个过程中操作数栈和局部变量表的变化:

  • iconst_1 将常量1入栈
索引 局部变量 操作数栈
0 1
1
  • istore_0 出栈并将值存入索引为0的局部变量中
索引 局部变量 操作数栈
0 1
1
  • iload_0 将索引为0的局部变量的值入栈
索引 局部变量 操作数栈
0 1 1
1
  • istore_1 出栈并将值存入索引为1的局部变量中
索引 局部变量 操作数栈
0 1
1 1
  • iinc 0 1 将索引为0的局部变量的值加上一个常量1
索引 局部变量 操作数栈
0 2
1 1
  • iload_1 将索引为1的局部变量的值入栈
索引 局部变量 操作数栈
0 2 1
1

由此可以看出,本例中test方法的返回值是1。

想一想,如果finally中也有return语句,那本例中test方法的返回值是多少?