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

勿忘初心2018-10-24 11:43

此文已由作者徐赟授权网易云社区发布。

欢迎访问网易云社区,了解更多网易技术产品运营经验。


一、 问几个问题

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

有这样一种结构

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

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信息,每个条目包含四个属性:

  • from:try代码块从哪一行字节码开始
  • to:try代码块到哪一行字节码结束
  • target:try代码块中出现异常后,程序应该跳转到哪一行字节码继续执行(Exception对应的catch代码开头)
  • type:捕获何种类型的异常

  通过这四个参数,是不是就能描述一段try...catch代码段了呢

2. java代码的throw exception; 语句会被编译成athrow执行(当然还包括之前的堆栈操作指令),athrow执行的执行逻辑是“

  • 首先到当前所在方法的Exception table中寻找是否有满足条件的try...catch代码块(堆栈中存放了抛出的异常对象和抛出异常的位置)
  • 如果存在符合条件的catch代码块,异常被消化掉
  • 如果不存在,则通过弹出栈帧的方式跳转到调用该方法的方法中(方法调用逻辑中,会将方法跳转现场信息压入线程栈),检测这一层方法的Exception table表中是否有满足条件的条目。
  • 如果异常一直没有被吃掉,就会逐级遍历相关方法,直到最顶层的main方法或run方法中,错误消息堆栈被打印到控制台,该线程被终止,这就算这个exception起义成功了。

   这就是try...catch的执行机制了。

3. 为什么finally这么坚强,不管try...catch搞出什么幺蛾子都会被执行,看看上面的字节码就知道了。

  • A代码执行过程如果不抛出异常,goto 66 会使代码跳转到C代码区(finally)执行,执行完就return了
  • A代码抛出FileNotFoundException异常,这是会在Exception table中锁定一下这条信息
      0    12    15   Class java/io/FileNotFoundException        这里target是15,会向goto一样将代码引导到B1处执行,随后接着执行C,再就return了
  • 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的执行机制就是这样,希望这篇文章无愧于标题所说的详解。


网易云免费体验馆,0成本体验20+款云产品! 

更多网易技术、产品、运营经验分享请点击