文章目录
代码已上传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
创作不易,觉得有帮助的,来个三连吧