异常的基本概念
Throwable
所有异常都是Throwable或者是Throwable的子类实例;
Exception
Exception涵盖程序可能需要捕获的异常,除了RuntimeException以外,其他类型的异常都需要显示的捕获或throws;
Error
当触发Error时,表示执行状态已无法恢复,需要终止线程或终止虚拟机;
RuntimeException&Error
这两个异常都属于非检查型异常,其他的异常都属于检查型异常,Java中所有的检查型异常都需要显示的捕获或者throws;
构造异常的成本
构造异常的成本十分昂贵,构造异常时,需要生成该异常的栈轨迹,因此虚拟机需要注意扫描当前线程的栈帧,并记录下方法名、类名、文件名以及第几行代码触发的异常等信息;
构造异常时直接从创建异常实例的位置开始算起;
捕获异常
捕获异常设计三个代码块,try、catch、finally;
try
用来标记需要监控异常的代码;
catch
用来捕获try代码块中触发的某种指定异常,try代码块后面可以跟多个catch用来捕获不同类型的异常,Java虚拟机会从上至下的匹配异常处理器;
finally
跟在try和catch代码块之后,用来声明一段必定运行的代码;
finally代码块执行时机
在程序正常执行的情况下,finally代码会在 try 代码块之后运行。否则,也就是 try 代码块触发异常的情况下,如果该异常没有被捕获,finally 代码块会直接运行,并且在运行之后重新抛出该异常。如果该异常被 catch 代码块捕获,finally 代码块则在 catch 代码块之后运行。在某些不幸的情况下,catch 代码块也触发了异常,那么 finally 代码块同样会运行,并会抛出 catch 代码块触发的异常。在某些极端不幸的情况下,finally 代码块也触发了异常,那么只好中断当前 finally 代码块的执行,并往外抛异常。
如果catch捕获了异常并且catch中的代码触发了另外一个异常,则finally只会抛出最后一个触发的异常;
虚拟机是如何捕获异常的
编译生成的字节码中,每个方法都有一个对应的异常表,表中的每一个条目代表一个异常处理器;异常表的条目由from指针、to指针、target指针以及所捕获的异常类型构成;
from指针 & to指针 & target指针
from和to表示该异常所要监控的范围,比如try代码块,target指针表示异常处理的起始位置,比如说catch代码块的起始位置;
public static void main(String[] args) {
try {
mayThrowException();
} catch (Exception e) {
e.printStackTrace();
}
}
// 对应的Java字节码
public static void main(java.lang.String[]);
Code:
0: invokestatic mayThrowException:()V
3: goto 11
6: astore_1
7: aload_1
8: invokevirtual java.lang.Exception.printStackTrace
11: return
// 上面代码生成的异常表
Exception table:
from to target type
0 3 6 Class java/lang/Exception // 异常表条目
当程序抛出异常时,会通过触发异常代码的索引值在异常表的from&to范围内进行查找,并判断异常是否和type类型匹配,如果匹配则执行target指针执行的字节码,如果遍历了异常表中所有的条目仍未匹配到处理器,则会弹出当前方法的栈帧并在调用者上重复上述操作;
finally代码块的编译
会把finally代码块的语句复制到每一个控制流的代码后面;
一个catch中捕获多个异常
虚拟机的实现方式是为多个异常生成了多个异常表条目;
// 在同一catch代码块中捕获多种异常
try {
...
} catch (SomeException | OtherException e) {
...
}
语法糖:Suppressed & try-witch-resource
Suppressed
允许把一个异常附于另一个异常之上,从而抛出的异常可以附带多个异常;
// 效果:
Exception in thread "main" java.lang.RuntimeException: Initial
at Foo.main(Foo.java:18)
Suppressed: java.lang.RuntimeException: Foo2
at Foo.close(Foo.java:13)
at Foo.main(Foo.java:19)
Suppressed: java.lang.RuntimeException: Foo1
at Foo.close(Foo.java:13)
at Foo.main(Foo.java:19)
Suppressed: java.lang.RuntimeException: Foo0
at Foo.close(Foo.java:13)
at Foo.main(Foo.java:19)
try-witch-resource
这个语法糖主要用来解决关闭多个资源的情况,try中允许声明实现了AutoCloseable接口的实例化类,编译器会自动添加对应的close;
该语法糖会自动使用Suppressed来避免多个异常被覆盖;
public class Foo implements AutoCloseable {
private final String name;
public Foo(String name) { this.name = name; }
@Override
public void close() {
throw new RuntimeException(name);
}
public static void main(String[] args) {
// try-with-resources
try (Foo foo0 = new Foo("Foo0");
Foo foo1 = new Foo("Foo1");
Foo foo2 = new Foo("Foo2")) {
throw new RuntimeException("Initial");
}
}
}