- 显式 抛异常的主体是 应用程序 ,使用 throw 关键字
- 隐式 抛异常的主体是 JVM ,在JVM执行过程中,碰到无法继续执行的异常状态时,自动抛出异常
- 例如ArrayIndexOutOfBoundsException
捕获异常
- try代码块
- 标记需要 异常监控 的代码
- catch代码块
- 定义了 针对指定类型的异常处理器
- 多个catch代码块,JVM会 从上至下 匹配异常处理器
- 前面catch代码块所捕获的异常类型不能覆盖后边的,否则编译器会报错
- finally代码块
- 声明一段 必定运行 的代码
- 程序正常执行,未抛出异常,try -> finally
- 程序抛出异常未被捕获,try(throw A) -> finally -> throw A
- 程序抛出异常并被捕获,try(throw A) -> catch(A) -> finally
- 程序抛出异常并被捕获,并且catch代码块也抛出异常,try(throw A) -> catch(A, throw B) -> finally -> throw B
- finally代码块抛出异常,中断finally代码块的执行,往外抛出异常
基本概念
继承关系
- 所有异常都是Throwable的子类
- Error
- 应用程序不应该捕获的异常
- 当程序触发Error时,已经无法恢复,需要 终止线程 甚至 终止JVM
- Exception
- 应用程序需要捕获的异常(RuntimeException除外)
- RuntimeException和Error属于 Java 的unchecked exception ,其他异常属于checked exception
- 所有checked exception都需要显式捕获或在方法声明中用throws关键词标注
- 通常情况下, 程序自定义的异常应该为checked exception ,以便于最大化利用Java编译器的 编译时检查
- 异常实例的构造是非常昂贵的
- 在构造异常实例时,JVM需要生成该异常的 栈轨迹(stack trace)
- 该操作会 逐一访问当前线程的Java栈帧,并且记录下各种调试信息
- 栈帧所指向方法的名字,方法所在的类名、文件名,以及在代码中的第几行触发该异常
- 在生成stack trace的时候,JVM会忽略掉异常构造器以及填充栈帧的Java方法(Throwable.fillInStackTrace),直接从新建异常位置开始算起
- JVM还会忽略标记为不可见的方法栈帧
- 即使代价昂贵,依旧不推荐缓存异常实例,容易误导开发人员
捕获异常的机制
Java代码
try { int a = 1; } catch (RuntimeException e) { int b = 2; } catch (Exception e) { int c = 3; }
字节码
stack=1, locals=3, args_size=1 0: iconst_1 1: istore_1 2: goto 14 5: astore_1 6: iconst_2 7: istore_2 8: goto 14 11: astore_1 12: iconst_3 13: istore_2 14: return Exception table: from to target type 0 2 5 Class java/lang/RuntimeException 0 2 11 Class java/lang/Exception
- 编译生成的字节码中, 每个方法都附带一个异常表 ,异常表中的每一个条目代表一个 异常处理器
- 条目组成:from指针、to指针、target指针和所捕获的异常类型
- 这里的指针的值指的是 字节码索引 ,用于定位字节码
- from指针和to指针(不含)标示了该异常处理器所监控的范围
- target指针指向异常处理器的起始位置
- 当程序触发异常时,JVM虚拟机会从上而下遍历异常表中的所有条目
- 当触发异常的字节码的索引值在某个异常表条目的监控范围内,JVM会判断所抛出的异常与该条目 想要捕获的异常 是否匹配
- 如果匹配,JVM会将控制流转移到该条目的target指针指向的字节码
- 如果遍历完所有异常条目,JVM仍未匹配到异常处理器,那么会 弹出当前方法所对应的栈帧 ,并且在 调用者 中重复上述操作
- 最坏情况:JVM需要遍历当前线程Java栈上所有方法的异常表
finally代码块
- 复制finally代码块的内容,分别放在try-catch代码块的 正常执行路径出口 以及 异常执行路径出口
- 针对异常执行路径,Java编译器会生成一个或多个异常表条目, 监控整个try-catch代码块 ,并且捕获所有种类的异常(any)
- 这些异常表条目的target指针将指向另一份复制的finally代码块
- 在这个代码块最后,Java编译器会重新抛出所捕获的异常
Java代码
public class Foo { private int tryBlock; private int catchBlock; private int finallyBlock; private int methodExit; public void test() { try { tryBlock = 0; } catch (RuntimeException e) { catchBlock = 1; } finally { finallyBlock = 2; } methodExit = 3; } }
字节码
stack=2, locals=3, args_size=1 0: aload_0 1: iconst_0 2: putfield #2 // Field tryBlock:I 5: aload_0 6: iconst_2 7: putfield #3 // Field finallyBlock:I 10: goto 35 13: astore_1 14: aload_0 15: iconst_1 16: putfield #5 // Field catchBlock:I 19: aload_0 20: iconst_2 21: putfield #3 // Field finallyBlock:I 24: goto 35 27: astore_2 28: aload_0 29: iconst_2 30: putfield #3 // Field finallyBlock:I 33: aload_2 34: athrow 35: aload_0 36: iconst_3 37: putfield #6 // Field methodExit:I 40: return Exception table: from to target type 0 5 13 Class java/lang/RuntimeException # 监控try代码块 0 5 27 any # 监控try代码块 13 19 27 any # 监控catch代码块
- 编译结果包含三份finallyBlock代码块
- 前两份分别位于try代码块和catch代码块的正常执行路径出口
- 最后一份作为异常处理器,监控try代码块和catch代码块,用于捕获两类异常
- try代码块触发的,未被catch代码块捕获的异常
- catch代码块触发的异常
- 如果catch代码块捕获了异常A,并且触发异常B,那么finally捕获并重新抛出的是异常B
- 原本的异常会被忽略掉 ,对于代码调试非常不方便
Supressed异常+语法糖
- Java 7引入 Supressed异常 来解决上述问题,允许 将一个异常附于另一个异常之上
- 抛出的异常可以附带多个异常的信息
- 语法糖: try-with-resources
- 在字节码层面自动使用Supressed异常
Java代码
@Data @AllArgsConstructor public class R implements AutoCloseable { private String name; @Override public void close() throws Exception { throw new RuntimeException(name); } public static void main(String[] args) throws Exception { try (R r1 = new R("R1"); R r2 = new R("R2")) { throw new RuntimeException("Init"); } } }
输出
Exception in thread "main" java.lang.RuntimeException: Init at me.zhongmingmao.basic.exception.R.main(R.java:19) Suppressed: java.lang.RuntimeException: R2 at me.zhongmingmao.basic.exception.R.close(R.java:13) at me.zhongmingmao.basic.exception.R.main(R.java:20) Suppressed: java.lang.RuntimeException: R1 at me.zhongmingmao.basic.exception.R.close(R.java:13) at me.zhongmingmao.basic.exception.R.main(R.java:20)
字节码
103: astore 9 105: aload_2 106: aload 9 // 自动使用Supressed异常 108: invokevirtual #11 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V 111: goto 118 114: aload_1 115: invokevirtual #10 // Method close:()V 118: aload 8 120: athrow
同时捕获多个异常
Java代码
public class T { public void test() { try { int a = 1; } catch (E2 | E1 e) { int b = 2; } } } class E1 extends RuntimeException { } class E2 extends RuntimeException { }
字节码
stack=1, locals=3, args_size=1 0: iconst_1 1: istore_1 2: goto 8 5: astore_1 6: iconst_2 7: istore_2 8: return Exception table: // 按顺序生成多个异常表条目 from to target type 0 2 5 Class me/zhongmingmao/basic/exception/E2 0 2 5 Class me/zhongmingmao/basic/exception/E1
转载请注明出处:http://zhongmingmao.me/2018/12/19/jvm-basic-exception/
访问原文「JVM基础 -- 异常处理」获取最佳阅读体验并参与讨论
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- golang 基础(33)异常处理
- Flutter尝鲜2——动画处理<基础>
- OpenCL:图像处理基础note
- Python基础数据处理库-Pandas
- [ Laravel 5.7 文档 ] 基础组件 —— 错误处理
- MySQL查询执行的基础——查询优化处理
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。