抛出异常可以分为显式和隐式两种。
显式抛出异常的主体是应用程序,它指的是在应用程序中使用"throw"关键字,手动将异常实例抛出。
隐式抛出异常的主体是Java虚拟机,它指的是Java虚拟机在执行的过程中,碰到无法继续执行的异常状态,自动抛出异常。例如数组索引越界异常(ArrayIndexOutOfBoundsException)。
捕获异常则涉及了以下代码块:
-
try代码块:用来标记需要进行异常监控的代码。
-
catch代码块,跟在try代码块之后,用来捕获在try代码块中触发的某种指定类型的异常。除了声明所捕获的异常类型之外,catch代码块还定义了针对该异常类型的异常处理器。在一个try代码块后面可以跟着多个catch代码块,来捕获不同类型的异常。java虚拟机会从上至下匹配异常处理器。因此,前面的catch代码块所捕获的异常类型不能覆盖后面的,否则编译器会出错
-
finally代码块:跟在try代码块和catch代码块之后,用来声明一段必定运行的代码.它的设计初衷是为了避免跳过某些关键的清理代码,例如关闭已经打开的系统资源。
异常的简单介绍
异常的简单介绍.png所有异常都是Throwable类或者其子类的实例。Throwable有两大直接子类。第一类是Error,涵盖程序不应该捕获的异常。当程序触发Error时,需要终止线程甚至是虚拟机。第二个子类是Exception,涵盖程序可能需要捕获并且处理的异常。
RuntimeException和Error属于Java里面的非检查异常,其他异常属于检查异常。所有的检查异常都需程序显式捕获,或者在方法声明中用throws关键字标注。
异常实例的构建是非常昂贵的,构建异常实例的时候,java虚拟机需要生成该异常的栈轨迹。该操作会逐一访问当前线程的Java栈帧并记录下各种调试信息。
java虚拟机是如何捕获异常的?
在编译生成的字节码中,每个方法都有一个异常表,异常表中的每一个条目代表着一个异常处理器,并且由from指针、to指针、target指针以及所捕获的异常类型构成,这些指针的值都是字节码索引,用以定位字节码。
其中:
from和to指针标示了该异常处理器所监控的范围,例如try代码块覆盖的范围,target指针则只想异常处理器的开始位置,例如catch代码块的起始位置。
当程序触发异常的时候,java虚拟机会从上到下遍历异常表中的所有条目。当触发异常的字节码索引值在某个异常表条目的监控范围内,java虚拟机会判断所抛出的异常和该条目想要捕获的异常是否匹配。如果匹配,java虚拟机会将控制流转移至该条目target指针指向的字节码。
如果遍历完所有的异常条目,Java虚拟机仍未匹配到异常处理器,那么它会弹出当前方法对应的Java栈帧,并且在调用者中重复上述操作,在最坏的情况下,Java虚拟机需要遍历当前线程Java栈的所有异常表。
finally代码块的编译比较复杂。当前版本java编译器的做法是复制finally代码块中的内容,分别放在try-catch代码块所有正常执行路径以及异常执行路径的出口中。
finally代码块复制.pngjava7中引入Supressed异常、try-catch-resources以及多异常捕获。后面两个可以精简我们的代码。
以上内容是我对极客时间郑雨迪老师<<深入拆解Java虚拟机>>的精简版总结。
网友评论