内容简介:最近写代码的时候遇到一些try catch的问题。在代码块1执行的时候发生异常,但是代码块2没有执行,代码块3执行了,排查半天发现代码块1中抛出的并不是Exception及其子类。那么没有catch住的try catch流程到底是怎么样的呢?之前也简单看过一些jvm try catch原理,这里尝试记录总结一下。
最近写代码的时候遇到一些try catch的问题。
try { 代码块1 } catch (Exception e) { 代码块2 } finally { 代码块3 } 复制代码
在代码块1执行的时候发生异常,但是代码块2没有执行,代码块3执行了,排查半天发现代码块1中抛出的并不是Exception及其子类。那么没有catch住的try catch流程到底是怎么样的呢?
之前也简单看过一些jvm try catch原理,这里尝试记录总结一下。
Java 在代码中通过使用 try{}catch(){}finally{}
块来对异常进行捕获或者处理。但是对于 JVM 来说,是如何处理 try/catch 代码块与异常的呢。
实际上 Java 编译后,会在代码后附加异常表的形式来实现 Java 的异常处理及 finally 机制(在 JDK1.4.2之前,javac 编译器使用 jsr 和 ret 指令来实现 finally 语句,但是1.4.2之后自动在每段可能的分支路径后将 finally 语句块内容冗余生成一遍来实现。JDK1.7及之后版本,则完全禁止在 Class 文件中使用 jsr 和 ret 指令)。
异常表
属性表(attribute_info)可以存在于 Class 文件、字段表、方法表中,用于描述某些场景的专有信息。属性表中有个 Code 属性,该属性在方法表中使用,Java 程序方法体中的代码被编译成的字节码指令存储在 Code 属性中。而异常表(exception_table)则是存储在 Code 属性表中的一个结构,这个结构是可选的。
异常表结构
异常表结构如下表所示。它包含四个字段:如果当字节码在第 start_pc 行到 end_pc 行之间(即[start_pc, end_pc))出现了类型为 catch_type 或者其子类的异常(catch_type 为指向一个 CONSTANT_Class_info 型常量的索引),则跳转到第 handler_pc 行执行。如果 catch_type 为0,表示任意异常情况都需要转到 handler_pc 处进行处理。
类型 | 名称 | 数量 |
---|---|---|
u2 | start_pc | 1 |
u2 | end_pc | 1 |
u2 | handler_pc | 1 |
u2 | catch_type | 1 |
处理异常机制
如上面所说,每个类编译后,都会跟随一个异常表,如果发生异常,首先在异常表中查找对应的行(即代码中相应的 try{}catch(){}
代码块),如果找到,则跳转到异常处理代码执行,如果没有找到,则返回(执行 finally 之后),并 copy 异常的应用给父调用者,接着查询父调用的异常表,以此类推。
异常处理实例
对于 Java 源码:
public class Test { public int inc() { int x; try { x = 1; return x; } catch (Exception e) { x = 2; return x; } finally { x = 3; } } } 复制代码
将其编译为 ByteCode 字节码(JDK版本1.8):
public int inc(); Code: 0: iconst_1 #try中x=1入栈 1: istore_1 #x=1存入第二个int变量 2: iload_1 #将第二个int变量推到栈顶 3: istore_2 #将栈顶元素存入第三个变量,即保存try中的返回值 4: iconst_3 #final中的x=3入栈 5: istore_1 #栈顶元素放入第二个int变量,即final中的x=3 6: iload_2 #将第三个int变量推到栈顶,即try中的返回值 7: ireturn #当前方法返回int,即x=1 8: astore_2 #栈顶数值放入当前frame的局部变量数组中第三个 9: iconst_2 #catch中的x=2入栈 10: istore_1 #x=2放入第二个int变量 11: iload_1 #将第二个int变量推到栈顶 12: istore_3 #将栈顶元素存入第四个变量,即保存catch中的返回值 13: iconst_3 #final中的x=3入栈 14: istore_1 #final中的x=3放入第一个int变量 15: iload_3 #将第四个int变量推到栈顶,即保存的catch中的返回值 16: ireturn #当前方法返回int,即x=2 17: astore 4 #栈顶数值放入当前frame的局部变量数组中第五个 19: iconst_3 #final中的x=3入栈 20: istore_1 #final中的x=3放入第一个int变量 21: aload 4 #当前frame的局部变量数组中第五个放入栈顶 23: athrow #将栈顶的数值作为异常或错误抛出 Exception table: from to target type 0 4 8 Class java/lang/Exception 0 4 17 any 8 13 17 any 17 19 17 any 复制代码
首先可以看到,对于 finally,编译器将每个可能出现的分支后都放置了冗余。并且编译器生成了三个异常表记录,从 Java 代码的语义上讲,执行路径分别为:
- 如果 try 语句块中出现了属于 Exception 及其子类的异常,则跳转到 catch 处理;
- 如果 try 语句块中出现了不属于 Exception 及其子类的异常,则跳转到 finally 处理;
- 如果 catch 语句块中出现了任何异常,则跳转到 finally 处理。
由此可以分析此段代码可能的返回结果:
- 如果没有出现异常,返回1;
- 如果出现 Exception 异常,返回2;
- 如果出现了 Exception 意外的异常,非正常退出,没有返回;
我们来分析字节码:
首先,0-3行,就是把整数1赋值给 x,并且将此时 x 的值复制一个副本到本地变量表的 Slot 中暂存,这个 Slot 里面的值在 ireturn 指令执行前会被重新读到栈顶,作为返回值。这时如果没有异常,则执行4-5行,把 x 赋值为3,然后返回前面保存的1,方法结束。如果出现异常,读取异常表发现应该执行第8行,pc 寄存器指针转向8行,8-16行就是把2赋值给 x,然后把 x 暂存起来,再将 x 赋值为3,然后将暂存的2读到操作栈顶返回。第17行开始是把 x 赋值为3并且将栈顶的异常抛出,方法结束。
上面是一个比较简单的Java程序,这里稍微复杂化它,尝试在finally中增加异常模块:
public class Test { public int inc() { int x; try { x = 1; return x; } catch (Exception e) { x = 2; return x; } finally { try{ x = 3; } catch (Exception e) { x = 4; } } } } 复制代码
将其编译为 ByteCode 字节码:
public int inc(); Code: 0: iconst_1 1: istore_1 2: iload_1 3: istore_2 4: iconst_3 5: istore_1 6: goto 12 9: astore_3 10: iconst_4 11: istore_1 12: iload_2 13: ireturn 14: astore_2 15: iconst_2 16: istore_1 17: iload_1 18: istore_3 19: iconst_3 20: istore_1 21: goto 28 24: astore 4 26: iconst_4 27: istore_1 28: iload_3 29: ireturn 30: astore 5 32: iconst_3 33: istore_1 34: goto 41 37: astore 6 39: iconst_4 40: istore_1 41: aload 5 43: athrow Exception table: from to target type 4 6 9 Class java/lang/Exception 0 4 14 Class java/lang/Exception 19 21 24 Class java/lang/Exception 0 4 30 any 14 19 30 any 32 34 37 Class java/lang/Exception 30 32 30 any 复制代码
和上面一样,0-3行为try内语句,保存x=1并准备返回,如果发生异常则查询异常表,跳转执行14行;14-18行为catch部分语句,保存x=2并准备返回;4-6行、19-21行、32-34行为finally中语句,首先设置x=3,如果没有发生异常,则之后进行跳转,否则往下执行,即执行 astore
, iconst
, istore
,即保留之前的栈顶位置,对x赋值为4。
最后总结一下,Java通过异常表来捕捉异常,在表中针对发生的异常能够获取接下来执行到哪里(从try跳转到catch),除了指定的异常外,还会自动追加any异常,用来捕获程序中没有捕获的异常。而finally会自动的追加到try、catch以及未捕获到的异常后面执行。对于多层次的 try{}catch{}
,同理。
ps. 最后有一个彩蛋,就是异常表后面会追加一个指向自己start_pc的条目,这里有一些讨论可以看看。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Zookeeper请求处理原理分析
- CSRF, XSS, Sql注入原理和处理方案
- Spring MVC源码(四) ----- 统一异常处理原理解析
- 一文详解大规模数据计算处理原理及操作重点
- rabbitmq 原理、集群、基本运维操作、常见故障处理
- rabbitmq 原理、集群、基本运维操作、常见故障处理
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Web Designer's Idea Book, Vol. 2
Patrick McNeil / How / 2010-9-19 / USD 30.00
Web Design Inspiration at a Glance Volume 2 of The Web Designer's Idea Book includes more than 650 new websites arranged thematically, so you can easily find inspiration for your work. Auth......一起来看看 《The Web Designer's Idea Book, Vol. 2》 这本书的介绍吧!