-
在程序正常的情况下finally代码块会在try代码块后面运行,如果try代码块抛出了异常,又没有被相应的catch代码块捕获,那么finally代码块会执行完后重新抛出该异常;
-
如果异常被catch代码块捕获,则finally代码块在catch代码块之后运行;
-
如果catch代码块也抛出了异常,那么finally代码块会运行,然后抛出catch代码块中抛出的异常;
-
如果finally代码块也触发了异常,那么中止finally代码块的执行,往外抛出异常;
-
异常实例的构造十分昂贵,因为他会逐一访问当前线程的JAVA栈帧,并记录下各种调试信息,包括栈帧所指向的方法名,方法所在的类名、文件名以及在代码的第几行触发异常;
-
我们无法通过缓存异常实例,在需要的时候抛出来优化开销,缓存的实例记录的是创建该实例时候的栈信息,无法有效反应出错时候的栈信息,如果使用fillInStackTrace()函数重新更新栈帧信息(主要开销来源),那么仅仅就省了对象创建的开销,那又何必大费周章的缓存实例呢,况且缓存实例还会带来其他问题;
-
在编译生成的字节码中,每个方法都带有一个异常表,异常表中的每一个条目都代表了一个异常处理器,由from指针、to指针、target指针、以及所捕获的异常类型组成;这些指针的值是字节码索引,用来定位字节码;
-
from指针 和 to指针 分别记录了该异常处理器所监控的范围,如try代码块所覆盖的范围,target指针则指向了异常处理器的起始位置,如catch代码块的起始位置;
-
当程序出现异常时,Java虚拟机会从上至下遍历异常表中所有的条目,当触发异常的字节码索引值在某个异常处理器监控的范围之内时,Java虚拟机会判断所抛出的异常和该条目想要捕获的异常是否匹配,如果匹配,Java虚拟机会将控制流转移至该条目target指针所指向的字节码;
-
如果遍历完所有的条目,仍未找到匹配的异常处理器,则弹出当前方法对应的Java栈帧,并且在调用者中重复上述操作。在最坏的情况下,Java虚拟机需要遍历当前线程Java栈上所有的异常表;
-
finally代码块的处理相对比较复杂, 目前Java编译器的做法是复制finally代码块的内容到try catch代码块的所有正常以及异常执行路径的出口中;
11.1 try代码块不抛出异常的正常执行路径出口复制finally代码块的内容;
11.2 try代码块抛出异常但是被catch代码块捕获并处理的执行出口,复制finally代码块的内容(个人觉得可以跟11.1中共用一份finally代码块,但实际上没有,还没有相通为什么) ;
11.3 try代码块抛出异常未被catch代码块捕获,或者catch代码块中也抛出了异常,此时finally的处理比较麻烦,编译器生成一条或者多条异常处理器,监控整个try catch代码段的执行,并且捕获的异常对象为任意异常,target指向finally代码块的另一个拷贝,在执行完finally代码块后,重新抛出异常;
-
示例代码及编译结果(来自郑雨迪博士的深入理解Java虚拟机):
public class Foo {
private int tryBlock;
private int catchBlock;
private int finallyBlock;
private int methodExit;
public void test() {
try {
tryBlock = 0;
} catch (Exception e) {
catchBlock = 1;
} finally {
finallyBlock = 2;
}
methodExit = 3;
}
}
$ javap -c Foo
…
public void test();
Code:
0: aload_0
1: iconst_0
2: putfield #20 // Field tryBlock:I
5: goto 30
8: astore_1
9: aload_0
10: iconst_1
11: putfield #22 // Field catchBlock:I
14: aload_0
15: iconst_2
16: putfield #24 // Field finallyBlock:I
19: goto 35
22: astore_2
23: aload_0
24: iconst_2
25: putfield #24 // Field finallyBlock:I
28: aload_2
29: athrow
30: aload_0
31: iconst_2
32: putfield #24 // Field finallyBlock:I
35: aload_0
36: iconst_3
37: putfield #26 // Field methodExit:I
40: return
Exception table:
from to target type
0 5 8 Class java/lang/Exception
0 14 22 any
…
-
如果catch代码块捕获了异常,并且又产生了异常,那么finally代码块捕获并重新抛出的异常将是catch代码块中产生的异常,原异常就丢失了;
-
Java 7 引入了Supressed的异常来解决13中提到的问题,允许开发人员将一个异常附于另一个异常之上,但语法晦涩,不易使用,try-with-resources语法糖将会自动使用Supressed异常;
-
如果finally语句中存在return语句会如何?如果finally中存在return语句,首先,函数的返回值只可能是finally中return的值,不管try catch代码段中是否存在return语句(因为return的对象其实也是一个可以改变的变量,finally中改变了return的对象的值),其次,如果try-catch代码段中产生了未被捕获的异常,那么这个异常将丢失,因为虽然finally生成的异常处理器捕获了异常,但因为在重抛异常之前return了,所以重抛异常语句得不到执行;
开心一下:为什么很多女孩子都有体香,而男的则很少有?因为化妆品、护肤品已经腌入味了!(?纯属调侃)