文章目录
前言
本博客讲解的是 JVM中对于异常处理的执行流程。提示:以下是本篇文章正文内容,下面案例可供参考
一、异常处理?
异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。异常处理就是针对这一类异常情况,进行捕获异常,让程序能处理运行时出现的任何意外或异常情况的方法。这个功能理清了编程者的思绪,也帮助代码增强了可读性,方便了维护者的阅读和理解。
二、try-catch
1.案例代码
代码如下(示例):
public class Demo1 {
public static void main(String[] args) {
int i = 0;
try {
i = 10;
}catch (Exception e) {
i = 20;
}
}
}
2.对应字节码指令
代码如下(示例):
Code:
stack=1, locals=3, args_size=1
0: iconst_0
1: istore_1
2: bipush 10
4: istore_1
5: goto 12
8: astore_2
9: bipush 20
11: istore_1
12: return
//多出来一个异常表
Exception table:
from to target type
2 5 8 Class java/lang/Exception
该处使用的的是javap进行反编译获取到的字节码指令。
-
我们能看到上面多出一个
Exception table
的异常表,上图from to
代表从[ 2 , 5)
行指令在运行过程中出现异常,会匹配后面的type
异常类型,如果匹配上,将会被target
跳转到第8条指令处继续执行。 -
第8条指令
astore_2
,是将异常对象对象存入局部变量位置为2的局部变量e
。
三、多个single-catch
1.案例代码
代码如下:
public class Demo1 {
public static void main(String[] args) {
int i = 0;
try {
i = 10;
}catch (ArithmeticException e) {
i = 20;
}catch (Exception e) {
i = 30;
}
}
}
2、对应的字节码指令
Code:
stack=1, locals=3, args_size=1
0: iconst_0
1: istore_1
2: bipush 10
4: istore_1
5: goto 19
8: astore_2 //被共用的局部变量 e
9: bipush 20
11: istore_1
12: goto 19
15: astore_2 //被共用的局部变量 e
16: bipush 30
18: istore_1
19: return
Exception table:
from to target type
2 5 8 Class java/lang/ArithmeticException
2 5 15 Class java/lang/Exception
- 因为异常出现,只能进入Exception table中的一个分支,所以局部变量表slot 2
( e )
是被共用的。
四、finally
1.案例代码
代码如下:
public class Demo2 {
public static void main(String[] args) {
int i = 0;
try {
i = 10;
} catch (Exception e) {
i = 20;
} finally {
i = 30;
}
}
}
2、对应的字节码指令
Code:
stack=1, locals=4, args_size=1
0: iconst_0
1: istore_1
//try块
2: bipush 10
4: istore_1
//try块执行完后,会执行finally
5: bipush 30
7: istore_1
8: goto 27
//catch块
11: astore_2 //异常信息放入局部变量表的2号槽位
12: bipush 20
14: istore_1
//catch块执行完后,会执行finally
15: bipush 30
17: istore_1
18: goto 27
//出现异常,但未被Exception捕获,会抛出其他异常,这时也需要执行finally块中的代码
21: astore_3
22: bipush 30
24: istore_1
25: aload_3
26: athrow //抛出异常
27: return
Exception table:
from to target type
2 5 11 Class java/lang/Exception
2 5 21 any
11 15 21 any
- 上图中我们发现
finally
块的代码指令被复制了3份,分别放在try块指令后,catch指令后,以及catch剩余的异常类型流程后。
注意:我们得注意虽然
finally
复制了3份,但是只会被执行一次。
四、finally中的return
1.案例代码
代码如下:
public class Demo3 {
public static void main(String[] args) {
int i = Demo3.test();
//结果为20
System.out.println(i);
}
public static int test() {
int i;
try {
i = 10;
return i;
} finally {
i = 20;
return i;
}
}
}
2、对应的字节码指令
Code:
stack=1, locals=3, args_size=0
//try块
0: bipush 10
2: istore_0
3: iload_0
4: istore_1 //暂存返回值,记住这里的操作,会将暂存值保存
//finally块
5: bipush 20
7: istore_0
8: iload_0
9: ireturn //ireturn会返回操作数栈顶的整型值20
//如果出现异常,还是会执行finally块中的内容,没有抛出异常
10: astore_2
11: bipush 20
13: istore_0
14: iload_0
15: ireturn //这里没有athrow了,也就是如果在finally块中如果有返回操作的话,且try块中出现异常,会吞掉异常!
Exception table:
from to target type
0 5 10 any
-
我们在上面发现只要
finally
块中存在return
语句,就算try
块中同样存在return
也只是会将返回值暂存,并不会返回,依然执行finally
块中的语句。 -
我们还发现如果出现了任何异常,都会跳转到第10条指令,但是直到结束我们也并没有看到
athrow
抛出异常,所以只要finally
存在return
语句,就会吞掉异常。我们将在下面的案例进一步解释:
五、finally中的吞噬异常
1.案例代码
代码如下:
public class Demo3 {
public static void main(String[] args) {
int i = Demo3.test();
//最终结果为20
System.out.println(i);
}
public static int test() {
int i;
try {
i = 10;
//这里应该会抛出异常
i = i/0;
return i;
} finally {
i = 20;
return i;
}
}
}
上面的代码执行也并未见抛出异常,不信的你们可以去试试 (v_v) 。
如果将上面finally
中的return
去掉就会出现报错了。
总结
以上就是今天要讲的内容,本文仅仅介绍了JVM中异常处理的指令,以及执行流程。
- 如果添加异常捕获,就会多出一个
Exception table
的异常表。 from to
代表从[ 2 , 5)
行指令在运行过程中出现异常,将会匹配后面的type
异常类型,target
就是将要跳转的第几条指令。- 异常出现,只能进入Exception table中的一个分支,所以会共享
e
局部变量。 finally
块的代码指令会被,放在try块指令后,catch指令后,以及catch剩余的异常类型流程后,并且只会被执行一次。- 只要
finally
存在return
语句,就会吞掉异常,try
块中如存在return
也只是会将返回值暂存。