参考资料:JVM如何处理异常深入详解
一、Java 异常的概念和分类
所有的异常都派生于Throwable类的一个实例
Error
java 运行时的系统错误和资源耗尽错误。 应用程序是不应该跑出这种类型的对象。一般出现错误,也没什么办法
Exception
RunTimeException和其他Exception(主要是IO Exception)
RunTimeException:为程序的逻辑错误
如:类型错误,数组越界,空指针
其他Exception(主要是IO Exception):程序本身没问题,但由于像I/O这样的错误导致异常
如:打开一个不存在的文件,根据字符串找class炸不到等
unchecked 和checked
java将error和RunTimeException归为unchecked 异常,其他为checked 异常
原则上checked 异常要进行声明,而unchecked 异常要么控制不了,如error,要么应该避免发生RunTimeException,不需要捕获
二、JVM如何处理异常
异常表 Exception Table
异常表的构成
from 可能发生异常的起始点
to 可能发生异常的结束点
target 上述from和to之前发生异常后的异常处理者的位置
type 异常处理者处理的异常的类信息异常表的调用
1.JVM会在当前出现异常的方法中,查找异常表,是否有合适的处理者来处理
2.如果当前方法异常表不为空,并且异常符合处理者的from和to节点,并且type也匹配,则JVM调用位于target的调用者来处理。
3.如果上一条未找到合理的处理者,则继续查找异常表中的剩余条目
4.如果当前方法的异常表无法处理,则向上查找(弹栈处理)刚刚调用该方法的调用处,并重复上面的操作。
5.如果所有的栈帧被弹出,仍然没有处理,则抛给当前的Thread,Thread则会终止。
6.如果当前Thread为最后一个非守护线程,且未处理异常,则会导致JVM终止运行。catch的顺序
catch的顺序决定了异常处理者在异常表的位置
因为我们实际中可以catch很多的exception 类型,所以越具体的exception要放在前面,宽泛的exception或者throwable一般都用于兜底finally的执行
finally是如何真正执行的,当前编译器的做法是复制finnal 代码块的内容,分别放在try-catch代码块的所有正常执行路径和异常执行路径的出口中
public static void XiaoXiao() {
try {
dada();
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("Finally");
}
}
//通过javap 反编译
public static void XiaoXiao();
Code:
0: invokestatic #3 // Method dada:()V
3: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
6: ldc #7 // String Finally
8: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
11: goto 41
14: astore_0
15: aload_0
16: invokevirtual #5 // Method java/lang/Exception.printStackTrace:()V
19: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
22: ldc #7 // String Finally
24: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
27: goto 41
30: astore_1
31: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
34: ldc #7 // String Finally
36: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
39: aload_1
40: athrow
41: return
Exception table:
from to target type
0 3 14 Class java/lang/Exception
0 3 30 any
14 19 30 any
这三份finally代码块都放在什么位置:
第一份位于try代码后 : 若果try中代码正常执行,没有异常那么finally代码就在这里执行。
第二份位于catch代码后 : 如果try中有异常同时被catch捕获,那么finally代码就在这里执行。
第三份位于异常执行路径 : 如果如果try中有异常但没有被catch捕获,或者catch又抛异常,那么就执行最终的finally代码
- return 和 finally
这个其实不必要强记,只需要你知道java编译器的原理就可以了,java编译器将finally编译进去后,如果有return是先执行finnal在执行return的
invokestatic #3 // Method testNPE:()V
3: ldc #6 // String OK
5: astore_0
6: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
9: ldc #8 // String tryCatchReturn
11: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
14: aload_0
15: areturn 返回OK字符串,areturn意思为return a reference from a method
三、未捕获异常是如何处理的
当一个线程遇到的异常,并没有被正确的捕获,就成为了未捕获异常,如没有catch或catch没有兜底的exception和throwable
-
如何设置未捕获异常
线程发生uncaughtException,JVM怎么处理
1.每个线程有一个变量uncaughtExceptionHandler来保存未捕获异常的handle
2.线程分发目标的handle,优先分发给当前线程的uncaughtExceptionHandler
3.上述为null时,分发给自己所在的ThreadGroup来作为未捕获异常处理者,ThreadGroup implements Thread.UncaughtExceptionHandler
4.ThreadGroup会尝试转给它的父ThreadGroup(如果存在的话)
5.如果上面没有找到对应的ThreadGroup,则尝试获取Thread.getDefaultUncaughtExceptionHandler()并分发