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 虚拟机》
- 正常路径执行 try 正常 - finally 正常(黑色实线)
- try 异常 - catch 住了异常 - finally 正常(黄色实线 + 黑丝虚线)
- try 异常 - catch 住了异常 - finally 抛出异常(黄色实线 + 黄色虚线)
- try 异常 - catch 也异常(例如又显式的跑出了异常) - 则 finally 重新抛出异常(红色实线 - 下半括号)
- 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 ======");
}
该方法有两个问题:
- 此时抛出的异常时 finally 的异常(原理见上边的执行路径图中的红色实线部分),通常 finally 中只是做一些资源关闭操作,当资源关闭发生异常时会覆盖更有价值的 catch 或者 try 中的异常信息
- 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 ======");
}
- 最终执行 close() 之后,抛出的异常是 catch 中的异常,不会被 close() 中的异常覆盖;
- 使用语法糖可以将多个 AutoCloseable 资源写在 try 括号中(用";"隔开),避免了“try-catch-finally hell”
- 值得注意的是,
try-with-resources 只支持实现了 AutoCloseable 的类
。
四、try-catch 逻辑为什么慢
1、
异常实例的构造十分昂贵
:构造异常实例时,Java 虚拟机需要生成该异常的栈轨迹。该操作会逐一访问当前线程的栈帧,并且记录下各种调试信息,包括栈帧所指向方法的名字,方法所在的类名、文件名,以及在代码中的第几行触发该异常。(《深入拆解 Java 虚拟机》,具体代码实现见《自己动手实现 Java 虚拟机》第10章)
2、try-catch 流程会影响 JVM 对代码的优化
(?)。
网友评论