字节码角度异常处理机制

概述

结构不佳的代码不能运行,这是java基本理念。发现错误的理想时机是编译期,但编译器不能发现所有错误,余下的问题就需要在运行期解决。程序发生异常需异常处理,把信息发送给特定的接收者处理。

异常分类

在这里插入图片描述
异常分为Error和Exception。Error错误发生,系统只能记录错误成因,安全退出。检查性异常,编译期就可以发现,运行异常,只能到程序运行时才能发现。

异常表

java编译后Class文件,Code是最重要的一个属性,把一个Java程序分为代码(Code,方法体里面的代码)和元数据(Metadata,类、字段、方法定义及其他信息)。Code用于描述代码,其他数据都用于描述元数据。Code属性中有异常表exception_table:

exception_table {
u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type; }

如果字节码在这个区间[start_pc,end_pc)出现了类型为catch_type或者其子类的异常catch_type为指向一个CONSTANT_Class_info型常量的索引),则转到handler_pc继续处理,当catch_type值为0时,代表任何异常都需要转到handler_pc处理。
由此看出java使用异常表,而不是简单的跳转实现异常处理。

异常处理

抛出异常(throw、throws)

遇到异常时,不做处理而是把它抛给调用者,由调用者根据情况处理。有可能是直接捕获并处理,也有可能继续上抛。抛出异常方法:throw,throws、系统自动抛出

  • throws作用在方法上,声明方法运行过程中可能会抛出异常,以便调用者根据不同的异常类型预先定义不同的处理方式。
  • throw作用在方法内抛出封装了异常信息的对象,程序执行到throw时,后续代码不再执行,而是跳转到调用者,并将异常信息抛给调用者。throw之后语句无法被执行,但是finally除外。

捕获(try…catch…finally)

捕获异常,针对性处理每种可能出现的异常,捕获异常后根据不同情况做不同处理。

        public int testFinally() {
            int x;
            try {
                x = 1;
                return x;
            } catch (Exception e) {
                x = 2;
                return x;
            } finally {
                try{
                    x = 3;
                } catch (Exception e) {
                    x = 4;
                }
            }
        }

testFinally方法体字节码:
在这里插入图片描述

 public int testFinally();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=7, args_size=1
         0: iconst_1//int类型常量1加载到栈顶
         1: istore_1//将栈顶int类型元素保存到局部变量表1的位置,此时x=1
         2: iload_1//将局部变量表1位置处的int类型元素加载到栈顶
         3: istore_2//将栈顶int型元素保存到局部变量表2的位置,保存的是return的值,此时是1
         4: iconst_3//将int常量3加载到栈顶
         5: istore_1//将栈顶int值存入局部变量表1的位置,此时x=3
         //finally也没发生异常跳转到字节码第12行开始执行,执行return x的动作
         6: goto     12
         /**看局部变量表[start_pc,end_pc)也就是4~5发生异常,跳转到第9行开
         * 始。也就finally中的try语句块x=3赋值操作如果发生异常,走它的
         * catch语句块
         */
         9: astore_3//将栈顶引用变量e存入当前栈帧的局部变量表3的位置
        10: iconst_4//将int常量4加载到栈顶
        11: istore_1//将栈int元素值保存到局部变量表1的位置,此时x=4
        //最外层try中的renturn x
        12: iload_2//局部变量表2位置上的int型变量加载到栈顶,此时是1
        13: ireturn//返回栈顶int型元素,此时返回1
         /**看局部变量表[start_pc,end_pc)也就是0~3发生异常,跳转到第14行开
         * 始。也就最外层的try语句块x=1赋值操作、返回值放入栈顶操作如果发生
         * 异常,走它的catch语句块
         */
        14: astore_2//将栈顶引用变量e存入当前栈帧的局部变量表2的位置
        15: iconst_2//int常量2加载到栈顶
        16: istore_1//栈顶int值保存到局部变量表1的位置,此时x=2,
        17: iload_1//局部变量表1位置的int数值加载到栈顶
        18: istore_3//将栈顶int类型数值存入局部变量表3的位置,存入的是2
        //finnaly
        19: iconst_3//int常量3加载的栈顶
        20: istore_1//栈顶int值保存到局部变量表1的位置,此时x=3
        //没有发生异常
        21: goto          28//没有发生异常跳转到28行,执行return操作
        /**看局部变量表[start_pc,end_pc)也就是19~20发生异常,跳转到第24行
         * 开始。也就finnaly的try语句块x=1赋值操作、返回值放入栈顶操作如果
         * 发生异常,走它的catch语句块
         */
        24: astore        4//将引用类型变量保存到局部变量表4的位置
        26: iconst_4//见常量4加载到栈顶
        27: istore_1//将栈顶int值保存到局部变量表1的位置,x=4
        28: iload_3//将局部变量表3处的int值加载到栈顶,2
        29: ireturn//将栈顶元素值返回,此时return 2
        /**
        * 看局部变量表[start_pc,end_pc)也就是0~3或14~18或者30发生异常不属
        * 于Exception及其子类的异常。最外层try语句块或者它的catch发生不属于
        * Exception,比如error 从30行执行。
        */
        30: astore        5//将引用类型变量(不属于Exception异常)保存到局部变量表5的位置
        //finnaly
        32: iconst_3//将常量3加载到栈顶
        33: istore_1//将栈顶元素值保存到局部变量表1的位置,此时x=3
        34: goto          41//没有发生异常转到字节码41行执行
        /**看局部变量表[start_pc,end_pc)也就是32~33发生异常,跳转到第37行
         * 开始。也就finnaly的try语句块x=3赋值操作如果发生异常,走它的
         * catch语句块
         */
        37: astore        6//异常e加载到栈顶6的位置
        39: iconst_4//int类型常量4加载到栈顶
        40: istore_1//栈顶元素保存到局部变量表1处,x=4的赋值操作
        41: aload         5//局部变量表5处的引用类型变量加载到栈顶
        43: athrow//抛出栈顶异常,不属于Exception及其子类的异常。其他Exception异常都会被捕获,没有throw不会抛出给上层调用者。
      Exception table:
         from    to  target type
             4     6     9   Class java/lang/Exception
             0     4    14   Class java/lang/Exception
            19    21    24   Class java/lang/Exception
             0     4    30   any
            14    19    30   any
            32    34    37   Class java/lang/Exception
            30    32    30   any

在这里插入图片描述
在这里插入图片描述
finally 程序块代码进行 “子程序调用”(finally 程序块被编译为嵌入式子程序)当 finally 程序块完成后,ret 2 指令将控制权返回到位于索引 4 的 jsr 指令之后的指令。

  • 1.6之前finally块靠jsr/ret两个指令完成,让代码精简,但不易读。其实知道jsr跳转前将下一条指令地址推入栈顶,跳转后第一时间将地址存入局部变量表,执行finally块结束时ret 局部变量表中存储的地址也不难
  • 1.6之后,字节码的每一个控制流后都加上finally块的字节码,少了jsr/ret两个指令易读,但是膨胀了字节码大小。

不管哪一种,只要finally块中有return,无正常/异常流结果都会被吞噬。

异常丢失

finally中有return

finally中的return会覆盖掉catch的异常。

 public  int foo() throws Exception
    {
        try {
            int a = 5/0;
            return 1;
        }catch(ArithmeticException amExp) {
            throw new Exception("aam");
        }finally {
            int b=90;
            return b;
            //throw new Exception("ab");
        }
    }
 public int foo() throws java.lang.Exception;
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=6, args_size=1
         0: iconst_5
         1: iconst_0
         2: idiv
         3: istore_1
         4: iconst_1
         5: istore_2
         6: bipush        90
         8: istore_3
         9: iload_3
        10: ireturn
        11: astore_1
        12: new           #3                  // class java/lang/Exception
        15: dup
        16: ldc           #4                  // String aam
        18: invokespecial #5                  // Method java/lang/Exception."<init>":(Ljava/lang/String;)V
        21: athrow
        22: astore        4
        24: bipush        90
        26: istore        5
        28: iload         5
        30: ireturn
      Exception table:
         from    to  target type
             0     6    11   Class java/lang/ArithmeticException
             0     6    22   any
            11    24    22   any

finally中有异常

finally异常会覆盖掉catch的异常。这也是reentrantLock的lock.lock()应该放到try外面的原因。如果获取锁的过程中发生异常,未能加锁成功,但最后finally始终会去unlock,这时候就会抛出IMSE(IllegalMonitorStateException)非法监视器状态异常,就会覆盖掉获取锁过程中抛出的异常

 public  int foo() throws Exception
    {
        try {
            int a = 5/0;
            return 1;
        }catch(ArithmeticException amExp) {
            throw new Exception("aam");
        }finally {
            int b=90;
            throw new Exception("ab");
        }
    }
 public int foo() throws java.lang.Exception;
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=6, args_size=1
         0: iconst_5
         1: iconst_0
         2: idiv
         3: istore_1
         4: iconst_1
         5: istore_2
         6: bipush        90
         8: istore_3
         9: new           #2                  // class java/lang/Exception
        12: dup
        13: ldc           #3                  // String ab
        15: invokespecial #4                  // Method java/lang/Exception."<init>":(Ljava/lang/String;)V
        18: athrow
        19: astore_1
        20: new           #2                  // class java/lang/Exception
        23: dup
        24: ldc           #6                  // String aam
        26: invokespecial #4                  // Method java/lang/Exception."<init>":(Ljava/lang/String;)V
        29: athrow
        30: astore        4
        32: bipush        90
        34: istore        5
        36: new           #2                  // class java/lang/Exception
        39: dup
        40: ldc           #3                  // String ab
        42: invokespecial #4                  // Method java/lang/Exception."<init>":(Ljava/lang/String;)V
        45: athrow
      Exception table:
         from    to  target type
             0     6    19   Class java/lang/ArithmeticException
             0     6    30   any
            19    32    30   any

由字节码可以看出

  • finallyreturn,退出栈帧的returntry语句结束就会出现,所以后续的异常处理都不会走
  • finally中抛出异常,抛出异常的athrow指令在所有抛出异常方法之前就抛出,后续代码也不会再走。
  • 捕获了异常,当我们没抛出。这时异常只是在局部变量表中存储着。

try-with-resource

jdk1.7开始引入try-with-resource将try-catch-finally 简化为 try-catch,在编译时会进行转化为 try-catch-finally 语句,我们就不需要在 finally块中手动关闭资源。在try-catch嵌套的代码可以确保正确的关闭资源。
在这里插入图片描述

Java异常处理和最佳实践
字节码分类助记

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值