java异常处理(二)—从字节码层面看throw关键字及try...catch...finally的实现

代码已上传gitee:https://gitee.com/shang_jun_shu/springboot-exception-jsr303.git
其他系列博客
Java异常处理(一)一异常处理流程
java异常处理(三)—Springboot全局异常处理(@ControllerAdvice和ErrorController)
java异常处理(四)—还在用if判断校验参数?试试SpringBoot全局异常+JSR303校验吧!

一、整体概括

在这里插入图片描述
java虚拟机对于异常的情况有两个处理方式,一个是抛出异常,一个是处理异常;

抛出异常中显式抛出异常(throw关键字)是由athrow指令实现,除此之外java虚拟机规范还规定了许多运行时异常会在其他java虚拟机指令检测到异常状况时自动抛出,例如除法运算,除数为0情况,虚拟机会在idiv或ldiv指令中抛出ArithmeticException异常。

另一个处理异常的方式,即catch语块则通过异常表实现

二、javap反编译字节码

举个例子,使用javap反编译DivException的class文件
在这里插入图片描述

三、throw显式抛出异常

public class DivException {
    void div(int num){
        if(num==0){
            throw new ArithmeticException("除数为0");
        }
    }
}

使用javap反编译字节码文件结果

Compiled from "DivException.java"
public class com.thinkcoder.exceptionjvm.DivException {
  public com.thinkcoder.exceptionjvm.DivException();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  void div(int);
    Code:
       0: iload_1
       1: ifne          14
       4: new           #2                  // class java/lang/ArithmeticException
       7: dup
       8: ldc           #3                  // String 除数为0
      10: invokespecial #4                  // Method java/lang/ArithmeticException."<init>":(Ljava/lang/String;)V
      13: athrow
      14: return
}

看反编译的字节码文件的13行,throw关键字是通过athrow指令实现的

四、jvm如何执行try…catch…finally

首先再来说下这三个语块的作用:

  • try:捕获异常
  • catch:处理异常
  • finally:最后执行的代码

4.1异常表及异常处理器

4.1.1 什么是异常表?

异常表存在于Code属性表中,而Java程序方法体里面代码经过编译后,最终变为字节码指令存储在Code属性中,那么有关如何处理异常的指令存储在异常表中。值得注意的是,异常表对于Code属性不是必须存在,也就是说并不是每个方法都有对应的异常表存在

4.1.2异常表结构?

Exception table:
   from    to  target type
       0     4     9   Class java/lang/ArithmeticException
       0     4    19   any
       9    14    19   any
      19    21    19   any

异常表由异常处理器组成,上述异常表由4条异常处理器组成;

  • 异常处理器由from指针、to指针、target指针,以及所捕获的异常类型所构成(type)
  • 三个指针的数值是字节码的行数,可以直接定位字节码,"行数"更确切的说是指的是,字节码相对于方法体开始的偏移量
  • from指针到to指针(不含该指针的行数),标识了该异常处理器监控字节码的范围
  • target指针,指向异常处理器的起始位置。如catch代码块的起始位置
  • type:捕获的异常类型,如Exception

4.1.3异常表作用?

从结构可以看出来,异常表实现了java中try…catch…finally的异常处理,对实现finally,jdk从1.7开始已经完全抛弃jsr和ret指令,也是使用异常表实现

4.2举个例子:

从例子中看如何实现try…catch…finally

java代码如下

public class DivException {
    void div(int num){
        int a;
        try{
            a = 5/num;
        }catch (ArithmeticException e){
            e.printStackTrace();
        }finally {
            a=1;
        }
    }
}

反编译class文件如下:

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

  void div(int);
    Code:
       0: iconst_5                 //将常数值5压入栈顶
       1: iload_1                  //将局部变量槽第一个值即m的值复制到操作数栈顶
       2: idiv                     //将栈顶两数值相除并将结果压入栈顶
       3: istore_2                 //将操作数栈顶数值即运算结果,存入到第2个局部变量槽中
       4: iconst_1                 //将常数值1压入栈顶
       5: istore_2                 //将操作数栈顶数值存入到第2个局部变量槽中
       6: goto          26         //执行偏移量26的指令
       9: astore_3                 //将操作数栈栈顶数值(异常实例e引用,此引用指向堆内存异常实例)存入到第3个局部变量槽中
      10: aload_3                  //将第3个局部变量槽即e引用复制到操作数栈顶
      11: invokevirtual #3         //调用实例e的printStackTrace方法   Method java/lang/ArithmeticException.printStackTrace:()V
      14: iconst_1                 //将常数值1压入操作数栈顶
      15: istore_2                 //将操作数栈顶数值存入到第2个局部变量槽中
      16: goto          26         //执行偏移量26的指令
      19: astore        4          //出现不属于ArithmeticException异常走这里,猜测是Throw接口类型的实例,将该实例存入到第4个局部变量槽中
      21: iconst_1                 //将常数值1压入栈顶
      22: istore_2                 //将操作数栈顶数值存入到第2个局部变量槽中
      23: aload         4          //将Throw实例e复制到操作数栈顶
      25: athrow                   //抛出去
      26: return                   //方法返回
    Exception table:
       from    to  target type
           0     4     9   Class java/lang/ArithmeticException
           0     4    19   any
           9    14    19   any
          19    21    19   any
}

字节码与java代码对应如下
在这里插入图片描述

4.3如何实现try

  • 带有try…catch的方法中,方法会携带一个异常表
  • 异常表中每个条目都是一个异常处理器
  • 触发异常时,JVM会遍历异常表,比较触发异常行数(方法开始的偏移量)是否在异常处理器from指针到to指针范围内
  • 范围匹配后,会比较异常类型和异常处理器中的type是否相同
  • 类型匹配后,会跳转到target指针所指向的字节码(catch代码块开始位置)
  • 如果没有匹配到异常处理器,会弹出当前方法对应的Java栈帧,并对调用者重复操作

4.4如何实现catch

如果try中的字节码在from指针和to指针范围内且异常类匹配,则执行target指针指向的字节码,此时执行catch块的代码块

4.5如何实现finally

我们知道finally是在try…catch…finally块中,除了极端情况发生最后都要执行的代码,那么jvm是如何实现这一现象呢?

就是将finally块的字节码复制了三份,对照上图可以发现,这三份代码分别放在了不同位置

  • try块后
  • catch块
  • 位于异常执行路径:如果try中有异常但没有被catch捕获,或者catch又抛异常,那么就执行最终的finally代码

这三个位置保证了finally的代码最终要被执行

参考博客

https://www.cnblogs.com/hulianwangjiagoushi/p/10901257.html

https://blog.csdn.net/feather_wch/article/details/82630303

创作不易,觉得有帮助的,来个三连吧
在这里插入图片描述

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序猿成长轨迹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值