JVM(八)-异常处理
参考文献
- 极客时间-JVM是如何处理异常的?
异常处理
- 异常处理的两大组成要素是抛出异常和捕获异常.这两大要素共同实现程序控制流的非正常转移.
- 抛出异常可分为显式和隐式两种.显式抛异常的主体是应用程序,它指的是在程序中使用
throw
关键字,手动将异常实例抛出. - 隐式抛异常的主体则是 Java 虚拟机,它指的是 Java 虚拟机在执行过程中,碰到无法继续执行的异常状态,自动抛出异常.举例来说,Java 虚拟机在执行读取数组操作时,发现输入的索引值是负数,故而抛出数组索引越界异常(
ArrayIndexOutOfBoundsException
). - 捕获异常则涉及了如下三种代码块:
try
代码块:用来标记需要进行异常监控的代码.catch
代码块:跟在try
代码块之后,用来捕获在try
代码块中触发的某种指定类型的异常.除了声明所捕获异常的类型之外,catch
代码块还定义了针对该异常类型的异常处理器.在 Java 中,try
代码块后面可以跟着多个catch
代码块,来捕获不同类型的异常.Java 虚拟机会从上至下匹配异常处理器.因此,前面的catch
代码块所捕获的异常类型不能覆盖后边的,否则编译器会报错.finally
代码块:跟在try
代码块和catch
代码块之后,用来声明一段必定运行的代码.它的设计初衷是为了避免跳过某些关键的清理代码,例如关闭已打开的系统资源.
Java
中的异常
-
在 Java 语言规范中,所有异常都是 Throwable 类或者其子类的实例.Throwable 有两大直接子类.
- 第一个是 Error,涵盖程序不应捕获的异常.当程序触发 Error 时,它的执行状态已经无法恢复,需要中止线程甚至是中止虚拟机.
- 第二子类则是 Exception,涵盖程序可能需要捕获并且处理的异常.
-
Exception 有一个特殊的子类 RuntimeException,用来表示“程序虽然无法继续执行,但是还能抢救一下”的情况.前边提到的数组索引越界便是其中的一种.
-
RuntimeException 和 Error 属于 Java 里的非检查异常(unchecked exception).其他异常则属于检查异常(checked exception).在 Java 语法中,所有的检查异常都需要程序显式地捕获,或者在方法声明中用 throws 关键字标注.通常情况下,程序中自定义的异常应为检查异常,以便最大化利用 Java 编译器的编译时检查.
-
异常实例的构造十分昂贵.这是由于在构造异常实例时,Java 虚拟机便需要生成该异常的栈轨迹(stack trace).该操作会逐一访问当前线程的 Java 栈帧,并且记录下各种调试信息,包括栈帧所指向方法的名字,方法所在的类名、文件名,以及在代码中的第几行触发该异常.
-
当然,在生成栈轨迹时,Java 虚拟机会忽略掉异常构造器以及填充栈帧的 Java 方法(Throwable.fillInStackTrace),直接从新建异常位置开始算起.此外,Java 虚拟机还会忽略标记为不可见的 Java 方法栈帧.
1 | public static void main(java.lang.String[]); |
- 编译过后,该方法的异常表拥有一个条目.
- from 指定字节码索引的开始位置
- to 指定字节码索引的结束位置
- target 异常处理的起始位置
- type 异常类型
- 只要在 from 和 to 之间发生了异常,就会跳转到 target 所指定的位置。
- 当程序触发异常时,Java 虚拟机会从上至下遍历异常表中的所有条目.当触发异常的字节码的索引值在某个异常表条目的监控范围内,Java 虚拟机会判断所抛出的异常和该条目想要捕获的异常是否匹配.如果匹配,Java 虚拟机会将控制流转移至该条目 target 指针指向的字节码.
- 如果遍历完所有异常表条目,Java 虚拟机仍未匹配到异常处理器,那么它会弹出当前方法对应的 Java 栈帧,并且在调用者(caller)中重复上述操作.在最坏情况下,Java 虚拟机需要遍历当前线程 Java 栈上所有方法的异常表.
- finally 代码块的编译比较复杂.当前版本 Java 编译器的做法,是复制 finally 代码块的内容,分别放在 try-catch 代码块所有正常执行路径以及异常执行路径的出口中.
1 | public class ExceptionTest { |
1 | public void test(); |
- 可以看到,编译结果包含三份 finally 代码块.其中,前两份分别位于 try 代码块和 catch 代码块的正常执行路径出口.最后一份则作为异常处理器,监控 try 代码块以及 catch 代码块.它将捕获 try 代码块触发的、未被 catch 代码块捕获的异常,以及 catch 代码块触发的异常.
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 HoleLin's Blog!