Java try-finally中的return语句 (从JVM Spec,栈的工作原理来分析)

原文来自我的个人博客 twodam.net

预备知识

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

  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语句中向上返回,重新抛出异常。

一个实例

static int test() {
    int x = 1;
    try {
        return x;
    } finally {
        ++x;
    }
}

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

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

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入栈
索引局部变量操作数栈
01
1
  • istore_0 出栈并将值存入索引为0的局部变量中
索引局部变量操作数栈
01
1
  • iload_0 将索引为0的局部变量的值入栈
索引局部变量操作数栈
011
1
  • istore_1 出栈并将值存入索引为1的局部变量中
索引局部变量操作数栈
01
11
  • iinc 0 1 将索引为0的局部变量的值加上一个常量1
索引局部变量操作数栈
02
11
  • iload_1 将索引为1的局部变量的值入栈
索引局部变量操作数栈
021
1

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

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

转载于:https://my.oschina.net/u/4037517/blog/2966117

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值