异常继承体系结构

在Java 中,类 Throwable 是整个异常处理机制的最高父类,它有两个子类 ErrorException ,分别代表Java中的「错误」和「异常」。

我们平常开发中提到的异常其实指的是 Exception ,因为 Error 对我们程序员而言是不可控制的,他往往是由于虚拟机内部出现问题导致的,例如:内存不足导致栈空间溢出,虚拟机运行故障等。一般这种情况,虚拟机将直接线程终止,并通过 Error 及其子类对象回调错误信息。

Exception 异常主要分为两类:

  • 检查异常 → IOException等 : 指编译器在编译期间要求必须得到处理的那些异常,比如写一段代码读写文件,而编译器认为读写文件很可能遇到文件不存在的情况,于是强制你写一段代码处理文件不存在的异常情况。
  • 非检查异常 → RuntimeException : 指编译器也不知道你的代码会出现哪些问题,于是就不强制你处理异常了,等到运行期间,如果出现异常,虚拟机会回调错误信息。

Java异常继承体系的一些常见示例:
https://raw.githubusercontent.com/Joker-5/image-host/master/img/20220513164411.png

异常处理过程

  1. 当程序遇到一个异常后,Java 会像创建其他对象一样创建一个异常类型的对象,并存储在堆中。
  2. 接着由异常机制接管程序,首先检索当前方法的异常表是否能匹配到该异常(异常表中保存了当前方法已经处理的所有异常集合)。
  3. 如果能过够匹配到,那么将根据异常表中保存的异常处理的相关信息,跳转到处理该异常的字节码位置继续执行。
  4. 否则,虚拟机将终止当前方法的调用并弹出该方法的栈帧,返回该方法的调用处,继续检索调用者的异常表能够匹配到该异常的处理。
  5. 如果一直无法匹配,最终整个方法调用链中涉及到的所有方法都会被弹栈,不会得到正常运行,并且最后虚拟机将打印这个异常的错误信息。

也就是说,如果一个异常得到了处理,那么程序将得到恢复并能够继续执行,否则的话所有涉及该异常的方法都将被终止运行。

Java异常处理的缺点

  • try-catch 代码段会产生额外的性能开销,所以一个建议是仅捕获有必要的代码段,尽量不要一个大的 try 包住整段的代码,同时,也尽量不要利用异常控制代码流程,而是采用条件语句(if-else等)来进行控制。
  • Java 每实例化一个 Exception,都会对当时的栈生成快照,这是一个比较重的操作。如果异常发生频繁,这个开销可就不能被忽略了。

    像一些追求性能的底层类库,一种优化方式就是「创建不生成栈快照的Exception」。但是这种方式本身也是存在问题的,因为这种方式的前提在于我在创建异常时需要知道未来是否会需要堆栈,但这在大型项目中几乎是不可能做到的,同时还引入了排查问题的难度,综合来看还是见仁见智吧。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐