JVM如何捕获异常?

Java异常知识

1.异常的两大关键因素

(1)抛出异常
1.显式:应用程序手动抛出异常。具体就是使用throw抛出异常
2.隐式:Java虚拟机对于没法执行的代码,自动抛出异常
(2)捕获异常
1.try 代码块:用来标记须要进行异常监控的代码。
2.catch 代码块:跟在 try 代码块以后,用来捕获在 try 代码块中触发的某种指定类型的异常。除了声明所捕获异常的类型以外,catch 代码块还定义了针对该异常类型的异常处理器。在 Java中,try 代码块后面能够跟着多个 catch 代码块,来捕获不一样类型的异常。Java 虚拟机会从上至下匹配异常处理器。所以,前面的 catch 代码块所捕获的异常类型不能覆盖后边的,不然编译器会报错。
3.fnally 代码块:跟在 try 代码块和 catch 代码块以后,用来声明一段一定运行的代码。它的设计初衷是为了不跳过某些关键的清理代码,例如关闭已打开的系统资源。在程序正常执行的状况下,这段代码会在 try 代码块以后运行。不然,也就是 try 代码块触发异常的状况下,若是该异常没有被捕获,fnally 代码块会直接运行,而且在运行以后从新抛出该异常。若是该异常被 catch 代码块捕获,fnally 代码块则在 catch 代码块以后运行。在某些不幸的状况下,catch 代码块也触发了异常,那么 fnally 代码块一样会运行,并会抛出 catch 代码块触发的异常。在某些极端不幸的状况下,fnally 代码块也触发了异常,那么只好中断当前 fnally 代码块的执行,并往外抛异常。java

2.异常的分类

图片描述

1.全部异常的父类都是Throwable
2.Error异常是程序的执行状态没法恢复的状态,只能停止线程甚至停止JVM的异常
3.Exception是相对Error没有这么严重的异常
4.Runtime Exception和Error都属于不须要检查的异常
5.除了Runtime Exception和Error的异常都是Check Exception异常
6.Check Exception异常都是须要显式捕获的异常

3.Java虚拟机是如何捕获异常的?

java虚拟机构造异常实例很是昂贵。虚拟机须要生成该异常的栈轨迹。该操做会逐一访问当前线程的 Java 栈帧,而且记录下各类调试信息,包括栈帧所指向方法的名字,方法所在的类名、文件名,以及在代码中的第几行触发该异常。
既然异常实例的构造十分昂贵,咱们是否能够缓存异常实例,在须要用到的时候直接抛出呢?从语法角度上来看,这是容许的。然而,该异常对应的栈轨迹并不是 throw 语句的位置,而是新建异常的位置。
所以,这种作法可能会误导开发人员,使其定位到错误的位置。这也是为何在实践中,咱们每每选择抛出新建异常实例的缘由。

异常处理器
1.来源:每一个方法在编译的时候都会生成一个异常表。异常表里面的每个条目都表明一个异常处理器。
2.组成:
(1)from指针,to指针:表明捕获异常的范围,就是Try的范围。
(2)target指针:表明处理器的开始位置,就是catch的起始位置。
(3)捕获的异常类型。
3.捕获异常
(1)当程序触发异常时,Java 虚拟机会从上至下遍历异常表中的全部条目。当触发异常的字节码的索引值在某个异常表条目的监控范围内,Java 虚拟机会判断所抛出的异常和该条目想要捕获的异常是否匹配。若是匹配,Java 虚拟机会将控制流转移至该条目 target 指针指向的字节码。
(2)若是遍历完全部异常表条目,Java 虚拟机仍未匹配到异常处理器,那么它会弹出当前方法对应的Java 栈帧,而且在调用者(caller)中重复上述操做。在最坏状况下,Java 虚拟机须要遍历当前线程 Java 栈上全部方法的异常表。
4.finally代码的编译:当前版本 Java 编译器的作法,是复制 fnally 代码块的内容,分别放在 try-catch 代码块全部正常执行路径以及异常执行路径的出口中。缓存

代码1:
Try{
Try block
} catch {
Catch block
} finally {
Finally block
}
代码2:
Try {
Try block
Finally block
} catch {
Catch block
Finally block
} finally{
Finally block
}
代码1是咱们的Java代码,代码2是编译以后的Java代码。

注意:若是 catch 代码块捕获了异常,而且触发了另外一个异常,那么 fnally 捕获而且重抛的异常是哪一个呢?答案是后者。也就是说本来的异常便会被忽略掉,这对于代码调试来讲十分不利。spa

5.Java7的 Supressed 异常以及语法糖

针对上节说的会将catch的异常忽略掉,Java7引入了 Supressed 异常处理这个问题。可是使用起来仍是很麻烦(没有感觉,😔)。
为此,Java 7 专门构造了一个名为 try-with-resources 的语法糖,在字节码层面自动使用Supressed 异常。固然,该语法糖的主要目的并非使用 Supressed 异常,而是精简资源打开关闭的用法。
资源的关闭操做自己容易触发异常。因此为了让每个资源都关闭,因此每个资源都要写个try catch 这样太过繁琐,因此try-with-resources就很好的解决了这个问题。程序能够在 try 关键字后声明并实例化实现了 AutoCloseable 接口的类,编译器将自动添加对应的 close() 操做。在声明多个AutoCloseable 实例的状况下,编译生成的字节码相似于上面手工编写代码的编译结果。与手工代码相比,try-with-resources 还会使用 Supressed 异常的功能,来避免原异常“被消失”。
除了 try-with-resources 语法糖以外,Java 7 还支持在同一 catch 代码块中捕获多种异常。实际实现很是简单,生成多个异常表条目便可。线程