美文网首页
夯实 Java 基础3 - 异常机制

夯实 Java 基础3 - 异常机制

作者: 原水寒 | 来源:发表于2019-02-24 15:47 被阅读1次

    Java 提供了完备的异常机制,在 Java7 引入了 try-with-resources 语法糖。

    • 一、Java 异常分类
    • 二、try-catch-finally 在字节码层面的实现
    • 三、try-with-resources
    • 四、try-catch 逻辑为什么慢

    一、Java 异常分类

    image.png

    图片来自《Java 36讲》
    Java异常分为 Error 和 Exception,绝大部分的 Error 会导致程序处于非正常不可恢复状态,不应捕获;Exception 表示可能需要捕获并且处理的异常。Exception 中只有 RuntimeException(运行时异常)是不需要捕获或声明抛出的,其他的 checked exception(检查型异常)需要捕获或声明抛出。

    二、try-catch-finally 在字节码层面的实现

    测试代码如下:

    public class TestException {
        public static void main(String[] args) {
            try {
                if (1 == 1) {
                    System.out.println("==========normal");
                    return;
                }
                throw new RuntimeException();
            } catch (RuntimeException e) {
                System.out.println("===========InterruptedException");
            } finally {
                System.out.println("===========finally");
            }
        }
    }
    

    javap -c TestException > TestException8.txt,查看字节码:

    image.png
    • 方法异常表:from ~ to 异常处理器监控的范围 [from, to);target 异常处理器的起始位置;type 所捕获的异常类型
    • 注意上述的两行 any 异常,第一行捕获 try([0, 8))中的异常,第二行捕获 catch([17, 26))中的异常

    可以看出实际上 finally 的代码会复制在 try 和 catch 块中的每一个路径出口处。那路径出口是什么?

    image.png

    图片来自《深入拆解 Java 虚拟机》

    1. 正常路径执行 try 正常 - finally 正常(黑色实线)
    2. try 异常 - catch 住了异常 - finally 正常(黄色实线 + 黑丝虚线)
    3. try 异常 - catch 住了异常 - finally 抛出异常(黄色实线 + 黄色虚线)
    4. try 异常 - catch 也异常(例如又显式的跑出了异常) - 则 finally 重新抛出异常(红色实线 - 下半括号)
    5. try 异常 - catch 没有捕获住该异常 - 则 finally 重新抛出异常(红色实线 - 上半括号)
    • 其中复制的红色的 finally 块,就是 jvm 捕获所有的异常,最后执行完 finally 块后(如果 finally 块发生异常,则直接抛出 finally 块发生的异常,快速失败了)再抛出这些异常(这也是字节码中的方法异常表中的 any 异常,注意上述的两行 any 异常,第一行捕获 try 中的异常,第二行捕获 catch 中的异常);
    • try 和 catch 中如果是 throw 语句,则后边不会给复制 finally 块了,就只有最后的 any 异常的 finally 块了。

    三、try-with-resources

    首先来看 Java7 之前的异常处理机制:

    try{
        System.out.println("======= try-normal ======");
        throw new RuntimeException("==== try-exception ====");
    } catch (Exception e) {
        throw new RuntimeException("====== catch-exception ======");
    } finally {
        throw new RuntimeException("====== finally-exception ======");
    }
    

    该方法有两个问题:

    1. 此时抛出的异常时 finally 的异常(原理见上边的执行路径图中的红色实线部分),通常 finally 中只是做一些资源关闭操作,当资源关闭发生异常时会覆盖更有价值的 catch 或者 try 中的异常信息
    2. try-catch-finally 一旦发生多层嵌套,可能会发生“try-catch-finally hell”,类似于“callback hell”,可读性很差

    Java7 的 try-with-resources 在一定程度上解决了上述问题(try-with-resources 只支持实现了 AutoCloseable 的类)。

    try-with-resources 使用姿势:

    public class MyCloseable implements AutoCloseable {
        public void doSomeThing() {
            System.out.println("========== doSomething ==========");
        }
    
        @Override
        public void close() throws Exception {
            System.out.println("========== close ==========");
            throw new RuntimeException("======= close-exception =======");
        }
    }
    
    ========== use =========
    try(MyCloseable mc = new MyCloseable()){
        mc.doSomeThing();
    } catch (Exception e) {
        throw new RuntimeException("====== catch-exception ======");
    }
    
    1. 最终执行 close() 之后,抛出的异常是 catch 中的异常,不会被 close() 中的异常覆盖;
    2. 使用语法糖可以将多个 AutoCloseable 资源写在 try 括号中(用";"隔开),避免了“try-catch-finally hell”
    3. 值得注意的是,try-with-resources 只支持实现了 AutoCloseable 的类

    四、try-catch 逻辑为什么慢

    1、异常实例的构造十分昂贵:构造异常实例时,Java 虚拟机需要生成该异常的栈轨迹。该操作会逐一访问当前线程的栈帧,并且记录下各种调试信息,包括栈帧所指向方法的名字,方法所在的类名、文件名,以及在代码中的第几行触发该异常。(《深入拆解 Java 虚拟机》,具体代码实现见《自己动手实现 Java 虚拟机》第10章)
    2、try-catch 流程会影响 JVM 对代码的优化(?)。

    相关文章

      网友评论

          本文标题:夯实 Java 基础3 - 异常机制

          本文链接:https://www.haomeiwen.com/subject/wbashqtx.html