JVM学习笔记——处理异常

2 篇文章 0 订阅

Java中的异常

简单回顾下Java中的异常,主要有两类:

  • 检查异常:必须在代码中显式处理的异常,若不处理则无法通过编译,处理手法有两种:采用try…catch或在方法上声明throws该异常来处理。
  • 非检查异常:与检查异常相反,不强制要求处理。

在Java中所有异常都是Throwbale或其子类的实例。如下图, 异常主要分为Error和Exception,其中Excption中还有特殊子类即RuntimeExption。RuntimeException和Error为非检查异常,其余异常基本属于检查异常

Error
Throwable
RuntimeException
Exception

JVM处理异常

代码经编译后,可以在字节码中看到每个方法都有一个异常表,其中的每行记录都代表着一个异常处理器,它由4个字段组成: from, to, target, type。[from,to)(左闭右开)指定了该异常处理器的作用域,target指向异常处理器的起始位置,type指定该异常处理器应处理的异常。如下代码编译:

public class ExceptionControlFlowDemo {
    public static void main(String[] args) {
        int tryInt,catchInt;
        try {
            tryInt = 1;
        } catch (Exception e) {
            catchInt = 2;
        }
    }
}

编译和并查看字节码:

javac ExceptionControlFlowDemo.java
javap -v ExceptionControlFlowDemo > ExceptionControlFlowDemo.javap
vi ExceptionControlFlowDemo.javap

取关键字节码:

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=4, args_size=1
         0: iconst_1 // 常量1
         1: istore_1
         2: goto          8
         5: astore_3
         6: iconst_2
         7: istore_2
         8: return
      Exception table: // 异常表
         from    to  target type
             0     2     5   Class java/lang/Exception

可以看到,异常表中指定了一条异常处理器,处理范围是[0, 2),异常处理器初始位置为5,处理的异常类型为Exception,这与上述代码一致,[0, 2)即[try, catch)中的”tryInt = 1“,5即catch代码块起始位置。

当触发异常时,JVM会从上到下遍历异常表每行记录,当抛出的异常和某行记录的type匹配时,则JVM将控制流转到该记录中target所指向的位置。

已知在try…catch处理异常时,我们还可以使用finally,在finally代码块中释放资源。目前根据Java编译器的不同,针对finally代码块的编译都略有不同(本文采用的openJDK(build 1.8.0_272-b10)附带的编译器),当前编译器会针对各个try和catch代码块在异常表中各自生成一条记录,作用域为try和catch代码块内的处理流程,处理的异常类型为Any,指代所有异常。

编译以下代码并查看字节码:

public class ExceptionControlFlowWithFinallyDemo {
    private int tryInt, catchInt, finallyInt, methodInt;
    private int i = 0;

    public void test() {
        try {
            tryInt = 0;
            if (i < 0) {
                return;
            } else {
                empty();
            }
        } catch (Exception e) {
            catchInt = 1;
        } finally {
            finallyInt = 2;
        }
        methodInt = 3;
    }

    public void empty() {}
}
javac ExceptionControlFlowWithFinallyDemo.java
javap -v ExceptionControlFlowWithFinallyDemo > ExceptionControlFlowWithFinallyDemo.javap
vi ExceptionControlFlowWithFinallyDemo.javap

截取关键字节码:

  public void test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: iconst_0
         2: putfield      #3                  // Field tryInt:I
         5: aload_0
         6: getfield      #2                  // Field i:I
         9: ifge          18
        12: aload_0
        13: iconst_2
        14: putfield      #4                  // Field finallyInt:I
        17: return
        18: aload_0
        19: invokevirtual #5                  // Method empty:()V
        22: aload_0
        23: iconst_2
        24: putfield      #4                  // Field finallyInt:I
        27: goto          52
        30: astore_1
        31: aload_0
        32: iconst_1
        33: putfield      #7                  // Field catchInt:I
        36: aload_0
        37: iconst_2
        38: putfield      #4                  // Field finallyInt:I
        41: goto          52
        44: astore_2
        45: aload_0
        46: iconst_2
        47: putfield      #4                  // Field finallyInt:I
        50: aload_2
        51: athrow
        52: aload_0
        53: iconst_3
        54: putfield      #8                  // Field methodInt:I
        57: return
      Exception table:
         from    to  target type
             0    12    30   Class java/lang/Exception
            18    22    30   Class java/lang/Exception
             0    12    44   any
            18    22    44   any
            30    36    44   any

可以看到异常表中有五行记录,而代码中仅显式捕获并处理了一个异常,这正是Java编译器将finally代码块编译后的字节码复制到了各个try…catch代码块内的处理流程末(在简单的控制流中,java编译器不会复制finally代码块的字节码,仅生成异常表中的记录,target指向finally起始位置)。

从上边的字节码中可以看出,即使采用了finally,当finally代码块中触发异常后即退出了方法栈,仍无法保证余下的代码块能够执行,即无法执行到return。在Java 7之前针对资源的处理仍需要在finally代码块中再做一些判断,如判断是否为null。在Java7之后,引入了try with resources语法,可以安全地关闭(TODO)。
同样可从字节码中看出的,当catch捕获到指定异常,但在处理异常的过程中引发了新的异常,那么finally处理的是新异常,原异常的信息(stack trace)将丢失。

总结

在Java中异常分为检查异常和非检查异常,检查异常需要在代码中显式处理,否则无法通过编译。

针对异常的处理,编译器会生成异常表,异常表由4个字段组成: from, to, target, type。[from,to)(左闭右开)指定了该异常处理器的作用域,target指向异常处理器的起始位置,type指定该异常处理器应处理的异常。

针对finally的处理,编译器会针对每个try和catch代码块在异常表中都生成一条记录,作用域为try和catch代码块内的处理流程,处理的异常类型为Any,指代所有异常。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值