java try...catch...finally机制详解

一、 问几个问题

在说明try...catch...finally机制前,先问几个问题作为文章的引子。

有这样一种结构

try {
      A;                 // A代表被try包裹的代码块
} catch (FileNotFoundException e) {
      B;
} finally {
      C;
}

1. 什么是try...catch...finally?
    当A代码块执行抛出FileNotFoundException异常时,B代码块被执行。无论A中是否有异常,C代码块都会被执行。

2. 如果A代码块中有return呢,C还会不会被执行?
    会被执行,另外B中如果有return,在A抛出FileNotFoundException异常的时,C依然会在B之后执行。

3. 如果A或B中抛出异常,C会执行么?
    会执行,而且finally执行后,异常会接着抛出去。

4. 什么情况下,C代码不会被执行?
    当代码进入try...catch区域后,基本上可以说finally代码块一定会被执行,只有两种极端的情况finally不会执行
        1) JVM crash了(不好不坏,偏偏在这个时间点crash)
        2) 当用户调用System.exit(0)主动停止JVM
    除此之外的任何情况,C代码都无可避免的被执行。


二、 解释

为什么try...catch...finally会表现出这样的特质,逻辑都蕴藏在java字节码里。
try {
      A;
} catch (FileNotFoundException e) {
      B1;
} catch (ClassNotFoundException e) {
      B2;
} finally {
      C;
}
这样结构的java代码编译成字节码后会变为如下形式

Code:

      stack=2, locals=3, args_size=1

         0: A  // 为了使结构清晰,这里用A代表A代码块的字节码逻辑
        12: goto          66

        15: B1 // catch了两个异常分别用B1,B2表示
        24: C
        32: goto          74

        35: B2
        44: C
        52: goto          74

        55: astore_2
        56: C
        64: aload_2
        65: athrow

        66: C
        74: return

      Exception table:
         from    to  target type
             0    12    15   Class java/io/FileNotFoundException
             0    12    35   Class java/lang/ClassNotFoundException
             0    24    55   any
            35    44    55   any

1. 带有try...catch...finally的方法会在java字节码中自动生成一个属于该方法的Exception table,记录了方法的try...catch信息,每个条目包含四个属性:
    a. from:try代码块从哪一行字节码开始
    b. to:try代码块到哪一行字节码结束
    c. target:try代码块中出现异常后,程序应该跳转到哪一行字节码继续执行(Exception对应的catch代码开头)
    d. type:捕获何种类型的异常
    通过这四个参数,是不是就能描述一段try...catch代码段了呢

2. java代码的throw exception; 语句会被编译成athrow执行(当然还包括之前的堆栈操作指令),athrow执行的执行逻辑是“
    a. 首先到当前所在方法的Exception table中寻找是否有满足条件的try...catch代码块(堆栈中存放了抛出的异常对象和抛出异常的位置)
    b. 如果存在符合条件的catch代码块,异常被消化掉
    c. 如果不存在,则通过弹出堆栈的方式跳转到调用该方法的方法中(方法调用逻辑中,会将方法跳转现场信息压入线程栈),检测这一层方法的Exception table表中是否有满足条件的条目。
    d. 如果异常一直没有被吃掉,就会逐级遍历相关方法,直到main方法或run方法中,错误消息堆栈被打印到控制台,该线程被终止,这就算这个exception起义成功了。
    这就是try...catch的执行机制了。

3. 为什么finally这么坚强,不管try...catch搞出什么幺蛾子都会被执行,看看上面的字节码就知道了。
    a. A代码执行过程如果不抛出异常,goto 66 会使代码跳转到C代码区(finally)执行,执行完就return了
    b. A代码抛出FileNotFoundException异常,这是会在Exception table中锁定一下这条信息
        0    12    15   Class java/io/FileNotFoundException        这里target是15,会向goto一样将代码引导到B1处执行,随后接着执行C,再就return了
    c. A代码抛出NullPointException,这个我们没有事先catch,但在搜索Exception table表时会找到如下信息
        0    24    55   any        这里的any类型保证了任何幺蛾子发生在0-24都会被捕获,代码会被引导到55处,这里通过注释的方式阐述:
            55: astore_2       // 从堆栈中拿出原始异常NullPointException对象,存放在临时变量2中
            56: C                // 执行C代码块(finally逻辑)
            64: aload_2        // 把临时变量2中的异常对象压入内存            65: athrow        // 把原始异常继续抛出
        总结起来就是,finally会响应try语句中抛出的异常,并且会将异常继续抛出去
        另外   (35    44    55   any)   这一句也保证了catch中抛出的异常也会被finally响应,并继续抛出

4. A或B代码中如果有return,C代码会插入到return之前,以保证C代码一定会被执行。

5. Exception table表中类型为any的条目只包裹了A,B代码块,C代码块并未被包裹,否则如果在C代码块抛出异常,逻辑就会陷死在C代码中(抛出的异常又被any捕获回来)

try...catch的执行机制就是这样,希望这篇文章无愧于标题所说的详解。

猜你喜欢

转载自blog.csdn.net/yzb808/article/details/51746552