原文链接:try-catch-finally字节码实例探究 | 代码段小站 ,欢迎访问我的博客
本文使用Idea的jclasslib插件查看字节码。本文全程自言自语,请勿自行代入。
概述
java是怎么处理try-catch-finally的?
需要了解的是,在class字节码中的属性表中,存在code
属性,它用于存放各方法的字节码、属性等。其中就包括exception_info
,即异常表,它记录了当一个异常发生后,应跳转到哪一行继续执行,而finally里面的代码块编译成字节码后则是被复制成许多份分别附在try和各catch块的后面。不了解这个原理没有关系,下面,我会从一个最简单的例子入手,带你走进try-catch-finally的世界。
这篇文章,我们就通过一些实例来看看,java中的try-catch-finally引申出的一些问题。
普通的例子
普通中的普通例子
我们先来一个普通的例子
public static void normal(){
try {
System.out.print("try-");
}catch (Exception e){
System.out.print("catch");
}finally {
System.out.print("finally");
}
}
这个相信大家都知道是输出try-finally,因为没有异常出现,那么它的字节码是:
0 getstatic #4 <java/lang/System.out>
3 ldc #5 <try->
5 invokevirtual #6 <java/io/PrintStream.print>
---------------- 上面这部分输出"try-"-----------------
8 getstatic #4 <java/lang/System.out>
11 ldc #7 <finally>
13 invokevirtual #6 <java/io/PrintStream.print>
---------------- 上面这部分输出"finally"--------------
16 goto 50 (+34)
-----------正常流程,跳转到50行直接return--------------
19 astore_0
20 getstatic #4 <java/lang/System.out>
23 ldc #8 <catch>
25 invokevirtual #6 <java/io/PrintStream.print>
-----上面这部分输出"catch",astore_0存入的是异常信息-----
28 getstatic #4 <java/lang/System.out>
31 ldc #7 <finally>
33 invokevirtual #6 <java/io/PrintStream.print>
36 goto 50 (+14)
-----注意上面这部分也是输出"finally",和之前某部分一样----
39 astore_1
40 getstatic #4 <java/lang/System.out>
43 ldc #7 <finally>
45 invokevirtual #6 <java/io/PrintStream.print>
48 aload_1
49 athrow
-----注意上面这部分也是输出"finally",但抛出了一个异常----
50 return
我们看看异常表:
如何解读:
- 0-8行(不包括8)的语句出现异常(类型为Exception,是我们catch的异常类型),交由19行处理(catch代码块),我们注意到28行执行完catch代码块后,就执行finally代码块。
- 0-8行的语句出现异常(类型为any,代表任何异常),交由39行处理,为什么这里会有这一项呢?是因为我们定义了finally,如果在try中出现了我们没有catch的异常类型,就可能会出现finally中的代码没有执行的情况,所以为了防止finally中的代码不执行,编译器会自动为我们暂时store这个异常信息(对应39行),执行完finally语句块后再抛出(对应49行)
- 19-28行的语句出现异常(类型为any,代表任何异常),交由39行处理。为什么这里会有这一项呢?原因和上面一样,如果执行catch代码块又出现了异常,也要保证finally代码块的执行。
普通例子举一反三
有了上面这个例子,我们就来思考一下,如果没有finally代码块,字节码会是什么样子的呢?
public static void normal(){
try {
System.out.print("try-");
}catch (Exception e){
System.out.print("catch");
}
}
首先能想到的是,正常流程执行完try中的内容就应该直接返回。而出现异常就应该跳转至中间一段用于处理异常的代码块。因为没有finally代码块,应该也不会有那么多冗余代码块被复制。
验证:
0 getstatic #4 <java/lang/System.out>
3 ldc #5 <try->
5 invokevirtual #6 <java/io/PrintStream.print>
----------上面输出"try-"-------------------------
8 goto 20 (+12)
------