美文网首页
你真的知道finally吗?(二)

你真的知道finally吗?(二)

作者: Misout | 来源:发表于2018-01-23 00:17 被阅读80次

微信公众号:Misout的博客
如有问题或建议,请留言

try catch finally的执行顺序

在上一篇文章【你真的知道finally吗?(一)】中,我们可以得出如下的结论:

finally 语句块是在 try 或者 catch 中的 return 语句或控制转移语句之前执行的

由此,可以轻松的理解之前finally中的各种现象。但深入到JVM底层我们就不得而知了。所以本文我们来一起探究JVM底层对try,catch中包含return语句是怎样的过程。本文需要有一定的JVM内存结构的知识,以及VM虚拟机栈的入栈,出栈等知识。

从字节码的角度看执行顺序

回顾下昨天的测试Demo,最终finallyCase()方法返回值是3,并非4。

package com.misout.grammar;

public class FinallyTest {

    public int finallyCase() {
        int i = 1;
        try {
            i = 3;
            return i;
        } finally {
            i = 4;
        }
    }
    
    public static void main(String[] args) {
        FinallyTest test = new FinallyTest();
        test.finallyCase();
    }
}

finallyCase()方法返回值:3
在CMD中进入FinallyTest.class所在目录,执行javap -c FinallyTest,得到如下的字节码:

public class com.misout.grammar.FinallyTest {
  public com.misout.grammar.FinallyTest();
    Code:
       0: aload_0
       1: invokespecial #8  // Method java/lang/Object."<init>":()V
       4: return

  public int finallyCase();
    Code:
       0: iconst_1
       1: istore_1
       2: iconst_3
       3: istore_1
       4: iload_1
       5: istore_3
       6: iconst_4
       7: istore_1
       8: iload_3
       9: ireturn
      10: astore_2
      11: iconst_4
      12: istore_1
      13: aload_2
      14: athrow
    Exception table:
       from    to  target type
           2     6    10   any

  public static void main(java.lang.String[]);
    Code:
       0: new           #1   // class com/misout/grammar/FinallyTest
       3: dup
       4: invokespecial #23  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: invokevirtual #24  // Method finallyCase:()I
      12: pop
      13: return
}

上面得到的字节码,是由一系列的指令符号组成。主要分为三部分:

  • public com.misout.grammar.FinallyTest():构造方法的指令。不是本文的重点。
  • public int finallyCase():finallyCase()方法执行的指令及执行顺序。
  • public static void main(java.lang.String[]):main方法的指令。

Java编译器输出的指令流,基本上是一种基于栈的指令集架构,也就是说指令的执行依赖于虚拟机栈(Java的一种内存空间)上的操作数栈来支持。虚拟机栈是线程私有的内存空间,每个方法在执行时会在虚拟机栈上创建一个栈帧,每个方法的执行相当于在虚拟机栈上的一个入栈和出栈的过程。每个栈帧包括:局部变量表、操作数栈、返回地址等空间,用于方法执行时存放局部变量,以及利用操作数栈做计算。本文涉及到的有局部变量表操作数栈,以及返回地址。在概念模型上,典型的栈帧模型图如下:

栈帧概念结构图

为了方便读者更好的理解本文,先看下文中涉及的字节码。在后面结合操作数栈来解释原理。下面列出了方法finallyCase()涉及到的字节码指令的含义:

  • 0: iconst_1:前面的0表示指令位于方法中指令的起始地址偏移。指令意思是将局部变量表中整型常量1加载到操作数栈顶,后缀1表示常量值1。
  • 1: istore_1:将操作数栈顶的整型值出栈,并保存到局部变量表的第1个slot区,这里的后缀1和iconst_1中的1不同,这里的1代表局部变量表的slot位置。
  • 4: iload_1:将局部变量表第1个slot区的整型值压入操作数栈顶。
  • ireturn:方法的返回指令,将结束方法的执行,并将操作数栈顶的值返回给方法,作为方法的返回值给调用方

基于栈的解释器指令执行过程

执行偏移地址为0的指令情况 执行偏移地址为1的指令情况 执行偏移地址为2的指令情况 执行偏移地址为3的指令情况 执行偏移地址为4的指令情况 执行偏移地址为5的指令情况
注意:在上图中,执行偏移地址为5的指令后即将执行finally块中指令。为什么此处将栈顶的3存入局部变量表的第3个slot而不是第2个slot,实际上是对返回结果的暂存 执行偏移地址为6的指令情况 执行偏移地址为7的指令情况 执行偏移地址为8的指令情况 执行偏移地址为9的指令情况:返回指令
分析到这里,已经得到我们想要的结果了。至于10~14的指令就不再继续分析了,有兴趣的同学可以继续查阅资料。

结论

  • Java是基于栈的指令集架构语言,所有的指令的执行都是基于入栈和出栈操作。和其他语言基于寄存器的操作方式有很大区别。
  • 通过上面的指令执行图,JVM在执行return语句前,会将结果值放入局部变量表的最后一个slot进行暂存,然后转去执行finally语句块。执行完成后,重新将slot暂存的值压入操作数栈然后返回。

参考

  • 深入理解Java虚拟机-JVM高级特性与最佳实践(第二版) 周志明 著

相关推荐

你真的知道finally吗?(一)
String你不知道的细节

相关文章

网友评论

      本文标题:你真的知道finally吗?(二)

      本文链接:https://www.haomeiwen.com/subject/tndraxtx.html