try-catch-finally的字节码原理

Java 中有一个非常重要的内容是 try-catch-finally 的执行顺序和返回值问题,其中 finally 一定会执行,但是为什么会这样? 下面看下 try-catch-finally 背后的实现原理

try-catch

public class Test {

    public static void main(String[] args) {
        foo();
    }

     public static void foo() {
       try {
           int i = 1 / 0;
       }catch (Exception e){
           System.out.println("执行异常");
           e.printStackTrace();
       }
    }

}

字节码

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

  public static void main(java.lang.String[]);
    Code:
       0: invokestatic  #2                  // Method foo:()V
       3: return

  public static void foo();
    Code:
       0: iconst_1                         // 将int 类型值1压栈到栈顶
       1: iconst_0						   // 将int 类型值0压栈到栈顶
       2: idiv                             // 将栈顶两int型数值相除并将结果压入栈顶
       3: istore_0                         // 将栈顶类型int数据存储到局部变量表下标0
       4: goto          20                 // 如果不抛异常跳到20行
       7: astore_0                         // 将引入对象(异常对象)存储局部变量表下标0
       8: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      11: ldc           #5                  // String 鎵ц寮傚父
      13: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      16: aload_0
      17: invokevirtual #7                  // Method java/lang/Exception.printStackTrace:()V
      20: return
    Exception table:
       from    to  target type
           0     4     7   Class java/lang/Exception
}

17 4: goto 20 // 如果不抛异常跳到20行

如果有异常抛出,如何处理呢?

当方法包含 try-catch 语句时,在编译单元生成的方法的 Code 属性中会生成一个异常表 (Exception table), 每个异常项表示一个异常处理器, 由 from 指针 、to 指针、target 指针 、所捕获的异常类型 type 四部分组成。这些指针的值是字节码索引,用于定位字节码。其含义是在 [from ,to) 字节码范围内,如果跑出来异常类型为 type 的异常,就会跳转到 target 指针表示的字节码处继续执行。

上面的例子中 Exception table表示,在 0 - 4 之间(不包含4),如果抛出类型为 Exception 或其子类就跳转到7继续执行

当抛出异常时,Java 虚拟机会自动将异常对象加载到操作数栈栈顶

多try-catch

public class Test {

    public static void main(String[] args) {
        foo();
    }

    public static void foo() {
       try {
           int i = 1 / 0;
       }catch (ArithmeticException e){
           System.out.println("执行异常 ArithmeticException");
           e.printStackTrace();
       } catch (NullPointerException e){
           System.out.println("执行异常 NullPointerException");
           e.printStackTrace();
       }

    }

}

字节码

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

  public static void main(java.lang.String[]);
    Code:
       0: invokestatic  #2                  // Method foo:()V
       3: return

  public static void foo();
    Code:
       0: iconst_1
       1: iconst_0
       2: idiv
       3: istore_0
       4: goto          36
       7: astore_0
       8: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      11: ldc           #5                  // String 鎵ц寮傚父 ArithmeticException
      13: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      16: aload_0
      17: invokevirtual #7                  // Method java/lang/ArithmeticException.printStackTrace:()V
      20: goto          36
      23: astore_0
      24: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      27: ldc           #9                  // String 鎵ц寮傚父 NullPointerException
      29: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      32: aload_0
      33: invokevirtual #10                 // Method java/lang/NullPointerException.printStackTrace:()V
      36: return
    Exception table:
       from    to  target type
           0     4     7   Class java/lang/ArithmeticException
           0     4    23   Class java/lang/NullPointerException
}

可以看到 ,多一个 catcha 语句处理分析, 异常表里面就会多一条记录,当程序出现异常时, Java 虚拟机会从上至下遍历异常表中所有的条目。当触发异常的字节码索引值在某个条目的 [from 、to)范围内,则会判断抛出的异常是否是想捕获的异常或子类

如果异常匹配, Java 虚拟机将控制跳转到 target 指向的字节码继续执行;如果不匹配,则继续遍历异常表。如果遍历完所有的异常表还未找到匹配的异常处理器,那么该异常将继续抛到调用方 (caller)中重复上述的操作

try-catch-finally

public class Test {

    public static void main(String[] args) {
        foo();
    }

    public static void foo() {
       try {
           int i = 1 / 0;
       }catch (ArithmeticException e){
           System.out.println("执行异常 ArithmeticException");
           e.printStackTrace();
       } finally {
           System.out.println("执行");
       }

    }

}

字节码

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

  public static void main(java.lang.String[]);
    Code:
       0: invokestatic  #2                  // Method foo:()V
       3: return

  public static void foo();
    Code:
       0: iconst_1                          // 将int 类型值1压栈到栈顶
       1: iconst_0                          // 将int 类型值0压栈到栈顶
       2: idiv                              // 将栈顶两int型数值相除并将结果压入栈顶
       3: istore_0							
                                    // 开始执行 finally 代码块
       4: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
       7: ldc           #4                  // String 鎵ц
       9: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      12: goto          50
      15: astore_0
                                    // 异常(被catch)情况下开始执行 finally 代码块
      16: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      19: ldc           #7                  // String 鎵ц寮傚父 ArithmeticException
      21: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      24: aload_0
      25: invokevirtual #8                  // Method java/lang/ArithmeticException.printStackTrace:()V
      28: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      31: ldc           #4                  // String 鎵ц
      33: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      36: goto          50
      39: astore_1
                                    // 异常(catch中执行出现异常)代码块出现异常 情况下开始									   执行 finally 代码块
      40: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      43: ldc           #4                  // String 鎵ц
      45: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      48: aload_1
      49: athrow
      50: return
    Exception table:
       from    to  target type
           0     4    15   Class java/lang/ArithmeticException
           0     4    39   any
          15    28    39   any
}

可以看出字节码中 出现三次调用

   getstatic     #3                     // Field java/lang/System.out:Ljava/io/PrintStream;
   ldc           #4                        // String 鎵ц
   invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V

都是在程序正常 return 和异常 throw 之前,其中两处在 try-catch 语句调用 return 之前,一处是在异常抛出 throw 之前

由代码可知,现在的 Java 编译器采用复制 finally 代码块的方式,并将其内容插入到 try 和 catch 代码块中所有正常退出和异常退出之前。这样就解释了我们一直以来所熟知的 finally 语句块一定会执行

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值