经过指令码来判断Java代码的执行顺序(++问题与return和finally的问题)

问题

在《深刻理解Java虚拟机》一书中遇到了以下代码:java

public int method() {
    int i;
    try {
        i = 1;
        return i;
    } catch (Exception e) {
        i = 2;
        return i;
    } finally {
        i = 3;
    }
}

因为曾经搜了一下return和finally的问题后,只是简单的看到了finally会执行,从而致使本身误觉得只是简单地把finally的执行顺序放到return语句以前,所以判断这段代码的执行结果应该是3,可实际运行结果是1。研究后发现本身当初真是太糊涂,因而便记录下来。工具

工具

咱们都知道,class文件中的内容就是可供JVM理解的字节码,JVM也是根据class的字节码来执行程序代码,因此class文件中就包含着程序代码最终的执行顺序。code

咱们能够经过官方提供的javap -c 再加上class文件的路径来获得各个方法对应的指令码。get

例如:javap -c Test.class虚拟机

引例

因为是打算使用JVM的指令码来解决这个问题,刚开始先以一个简单的方法来讲明一下。对于以下方法:io

public int method1() {
    int i = 1;
    return i;
}

该方法对应的指令码为:table

public int method1();
    Code:
       0: iconst_1
       1: istore_1
       2: iload_1
       3: ireturn

每一个指令对应着一个操做,上面的指令码意思是:ast

  1. 将int型数值0推送至栈顶
  2. 将栈顶int型元素存入第二个空间中
  3. 将第二个空间的int型元素推送至栈顶
  4. 返回将栈顶的int型元素并退出这个方法

由此能够看出,经过指令码,咱们能够直观地看到程序代码的执行顺序,这对于解决任何执行顺序的问题是一个利器。class

若是仍是感受有些不明因此,那咱们能够再看看i++++i的问题。对于以下代码:变量

// return 1
public int method2() {
    int i = 1;
    return i++;
}

// return 2
public int method3() {
    int i = 1;
    return ++i;
}

它们的指令码分别是:

public int method2();
    Code:
       0: iconst_1
       1: istore_1
       2: iload_1
       3: iinc          1, 1
       6: ireturn

  public int method3();
    Code:
       0: iconst_1
       1: istore_1
       2: iinc          1, 1
       5: iload_1
       6: ireturn

显然,这两段指令码最大的区别就是iinc 1,1指令的位置不一样,并且若是把这条指令删除,那么与method1的指令码彻底一致,对应源代码来看,这条指令就是++这个符号的影响了。

而这个关键的iinc 1,1指令的做用哪怕彻底不懂也能猜出来,就是将第二个空间的int数据+1后再放回第二个空间

将这个含义放到指令码中再从新捋一遍,以method2为例:

  1. 将int型数值0推送至栈顶
  2. 将栈顶int型元素存入第二个空间中
  3. 将第二个空间的int型元素(1)推送至栈顶
  4. 将第二个空间的int数据+1后再放回第二个空间
  5. 返回将栈顶的int型元素并退出这个方法

须要注意的是,第三步是将1而不是整个空间推送至栈顶,因此第四步对第二个空间中的数据1加1后并无改变栈顶的值,所以返回值为1。相对的,method2则是:

  1. 将int型数值0推送至栈顶
  2. 将栈顶int型元素存入第二个空间中
  3. 将第二个空间的int数据+1后再放回第二个空间
  4. 将第二个空间的int型元素(2)推送至栈顶
  5. 返回将栈顶的int型元素并退出这个方法

因此,返回的是2。

解决

如今咱们能够看最初的method方法了,在这里再复制一遍代码:

public int method() {
    int i;
    try {
        i = 1;
        return i;
    } catch (Exception e) {
        i = 2;
        return i;
    } finally {
        i = 3;
    }
}

对应的指令码:

public int method();
    Code:
       0: iconst_1
       1: istore_1
       2: iload_1
       3: istore        4
       5: iconst_3
       6: istore_1
       7: iload         4
       9: ireturn
      10: astore_2
      11: iconst_2
      12: istore_1
      13: iload_1
      14: istore        4
      16: iconst_3
      17: istore_1
      18: iload         4
      20: ireturn
      21: astore_3
      22: iconst_3
      23: istore_1
      24: aload_3
      25: athrow
    Exception table:
       from    to  target type
           0     5    10   Class java/lang/Exception
           0     5    21   any
          10    16    21   any

这段指令码不一样的地方在于最后有一个异常表,咱们先不用管它,先看到第一个ireturn指令的指令码,即代码中的第9行为止的指令码:

0: iconst_1
1: istore_1
2: iload_1
3: istore        4
5: iconst_3
6: istore_1
7: iload         4
9: ireturn

这段指令码就是当没有异常时,程序执行的指令码,finally语句块的指令码已经包含在里面了:

  1. 将int型数值1推送至栈顶
  2. 将栈顶int型元素存入第二个空间中
  3. 将第二个空间的int型元素(1)推送至栈顶
  4. 将栈顶int型元素存入第五个空间中
  5. 将int型数值3推送至栈顶
  6. 将栈顶int型元素存入第二个空间中(3
  7. 第五个空间的int型元素(1)推送至栈顶
  8. 返回将栈顶的int型元素并退出这个方法

由此能够看出,方法返回的是第五个空间的1而不是第二个空间的3,和运行结果一致。

其中,关键的地方就是第四步以及第七步。因而可知,Java程序在执行时遇到return语句时,会先将方法的返回值保存起来,若是还有finally语句块,那么就先执行finally语句块,最后再将返回值取出后返回

另外,若是return后跟的是表达式或者方法,那么会先计算出最终的返回值后再执行finally语句块,可自行验证。

固然,若是保存的返回值是一个引用类型的变量,那么在finally代码块中修改则会改变这个变量自己的属性,于是改变返回值的属性,毕竟finally的代码是的的确确执行过了。

例如,返回一个List,在finally中又对List进行了增长或删除,那么返回的List的内容天然也变了。

附加

关于指令码其他的部分,涉及到更多知识,在这里根据个人理解简单说一下。

这段指令码最后有一个异常表,它的含义能够简单解释为:在[from,to)的区间内,若是发生type类型的异常,那么就跳到target执行。

正由于有了异常表的存在,在出现异常时,程序能够根据产生的异常来跳到正确的位置执行接下来的代码。

[10,20]即为catch代码块对应的指令码,不过其中会把捕捉到的异常存储下来,也就是源代码中的Exception e。[21,25]则是会把try语句块中抛出的catch没有捕捉的异常保存下来,而后执行finally的代码,最后抛出该异常结束方法。

这三片指令码都包含了finally的指令码,也就保证了源代码中finally的代码确定会执行。

结论

Java程序在执行时遇到return语句时,会先将方法的返回值保存起来,若是还有finally语句块,那么就先执行finally语句块,最后再将返回值取出后返回。另外,若是return后跟的是表达式或者方法,那么会先计算出最终的返回值后再执行finally语句块。

笔记内容只是本人思考而写,若是有什么问题,还请指出,谢谢!

相关文章
相关标签/搜索