深入jvm 05. 异常处理流程

0、异常的概念

所有异常都是 Throwable 类或者其子类的实例。Throwable 的两大直接子类,分别是ErrorException。Error是编译时错误和系统错误,系统错误在除特殊情况下,都不需要你来关心,基本不会出现。而编译时错误,如果你使用了编译器,那么编译器会提示。而Exception是程序能捕获和处理的异常。

RuntimeException是Exception的一个子类,表示运行时异常,这种异常我们不需要处理,即不要声明也不要处理这个异常,完全由虚拟机接管,比如空指针异常。RuntimeException和Error属于非检查异常,其他异常为检查异常,所有的检查异常都需要显示地捕获,或者方法声明中用 throws 标注。

1、异常抛出的形式

抛出异常,形式上分为两种:
1)使用"throw"关键字,显式抛出异常实例
2)虚拟机在执行过程中,遇到无法继续执行的异常状态,自动抛出异常,比如数组索引越界异常(ArrayIndexOutOfBoundsException)。

2、异常处理流程涉及的三种代码块

1)try 代码块:可能会发生异常的业务代码块。

2)catch 代码块:捕获try代码块中触发的某种指定类型的异常,并进行响应的异常处理。catch块可以有多个,用来捕获不同类型的异常。jvm在异常匹配的时候,是从上到下遍历的,上面的异常不能覆盖下面的异常的。

3)finally 代码块:一段必定会运行的代码块。一般用来关闭已打开的系统资源,比如锁资源。

3、jvm 捕获异常的方式——异常表

在这里插入图片描述
经javac编译生成的字节码文件中,每个方法都有一张异常表。异常表的每一行代表一个异常处理器,它是由from指针、to指针、target指针以及所捕获的异常类型组成,指针表示的是字节码文件中方法的code行号(非源代码的行号),如图所示。

其中,from 指针和 to 指针指示了该异常处理器监控异常的范围,比如try代码块中的某一段区间。target指向异常处理的起始位置,比如catch块的起始位置。

当程序触发异常时,是从上到下依次匹配异常表中的条目,判断发生异常的字节码索引是否在本异常处理的监控范围内,以及异常类型是否匹配。如果匹配,则转而执行target所指向的代码。如果异常表中都不匹配,则会弹出当前方法的栈帧,在调用者中重复上述操作。

4、异常发生、捕获和finally块的执行流程

在这里插入图片描述
finally块的内容,有两份放在try块和catch块的正常执行路径出口,还有一份finally作为异常处理器,监控try块和catch块,它将捕获try块触发,但catch块没有捕获的异常,或者catch块触发的异常。

1)如果try块中一切正常,则执行完后,执行finally块中的代码。
2)如果try块中发生异常,且被catch捕获,则执行catch块中的代码。在catch中不发生异常的情况下,执行finally中的内容。
3)如果try块中发生异常,且被catch捕获,在执行catch中的代码时又发生了异常,则也要执行finally中的内容,然后finally再向外抛出catch块中的异常。极端情况下,finally也抛出异常,则中断当前finally的运行,并往外抛异常。
4)如果try块中的异常,没有被catch捕获,则执行finally中的代码,然后由finally抛出这个异常。

注意:如果finally块中有return语句,catch内抛出的异常会被忽略,因为finally捕获了catch中的异常后,先要指向finally的代码,然后再抛出这个异常。所以在重新抛出异常前,程序就返回了。

5、异常捕获为什么比较耗费性能

从 jvm 的角度来看,在构造异常实例时,jvm 需要生成该异常的栈轨迹(stack trace)。该操作会依次访问当前线程的java栈帧,并且记录下各种调式信息,包括栈帧所指向方法的名字,方法所在的类名、文件名,以及在代码的哪一行触发的异常。

猜你喜欢

转载自blog.csdn.net/Longstar_L/article/details/107563077