也就是告诉 JVM,如果出异常了,请从这里开始处理。
最后,看 catch_type 参数,对应的是 Exception table 里面的 type。
这里就是程序捕获的异常。
比如我把程序修改为这样,捕获三种类型的异常:
别问,问就是语法规定。
具体是啥语法规定呢?
就在异常表的这个地方:
编译器会检查该类是否是 Throwable 或 Throwable 的子类。
关于 Throwable、Exception、Error、RuntimeException 就不细说了,生成一个继承关系图给大家看就行了:
所以,上面的消息汇总一下:
- from:可能发生异常的起始点指令索引下标(包含)
- to:可能发生异常的结束点指令索引下标(不包含)
- target:在from和to的范围内,发生异常后,开始处理异常的指令索引下标
- type:当前范围可以处理的异常类信息
知道了异常表之后,可以回答这个问题了:异常怎么被抛出的?
JVM 通过异常表,帮我们抛出来的。
异常表里面有啥?
前面我说了,不再赘述。
异常表怎么用呢?
简单描述一下:
1.如果出现异常了,JVM 会在当前的方法中去寻找异常表,查看是否该异常被捕获了。
2.如果在异常表里面匹配到了异常,则调用 target 对应的索引下标的指令,继续执行。
好,那么问题又来了。如果匹配不到异常怎么办呢?
我在官网文档的这里找到了答案:
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-3.html#jvms-3.12
它的示例代码是这样的:
然后下面有这样的一句描述:
意思就是如果抛出的值与 catchTwo 的任何一个 catch 子句的参数不匹配,Java虚拟机就会重新抛出该值,而不调用 catchTwo 的任何一个 catch 子句中的代码。
什么意思?
说白了就是反正我处理不了,我会把异常扔给调用方。
这是编程常识,大家当然都知道。
但是当常识性的东西,以这样的规范的描述展示在你面前的时候,感觉还是挺奇妙的。
当别人问你,为什么是这样的调用流程的时候,你说这是规定。
当别人问你,规定在哪的时候,你能把官网文档拿出来扔他脸上,指着说:就是这里。
虽然,好像没啥卵用。
稍微特殊的情况
这一趴再简单的介绍一下有 finally 的情况:
public class MainTest { public static void main(String[] args) { try { int a = 1 / 0; } catch (Exception e) { e.printStackTrace(); } finally { System.out.println("final"); } } }
经过 javap 编译后,异常表部分出现了三条记录:
第一条认识,是我们主动捕获的异常。
第二三条都是 any,这是啥玩意?
答案在这:
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-3.html#jvms-3.13
主要看我画线的地方:
一个带有 finally 子句的 try 语句被编译为有一个特殊的异常处理程序,这个异常处理程序可以处理在 try 语句中抛出的(any)任何异常。
所有,翻译一下上面的异常表就是:
- 如果 0 到 4 的指令之间发生了 Exception 类型的异常,调用索引为 15 的指令,开始处理异常。
- 如果 0 到 4 的指令之间,不论发生了什么异常,都调用索引为 31 的指令(finally 代码块开始的地方)
- 如果 15 到 20 的指令之间(也就是 catch 的部分),不论发生了什么异常,都调用索引为 31 的指令。
接着,我们把目光放到这一部分: