Java中的异常表(Exception table)

Java 代码中通过使用 try-catch-finally 块来对异常进行捕获/处理。但是对于 JVM 来说,是如何处理 try/catch 代码块与异常的呢?
实际上 Java代码在进行编译时,编译器会在代码后附加一个异常表,以实现try块出现异常后能进入对应的异常处理程序执行。

  • 如果在方法执行期间抛出异常,Java 虚拟机会在异常表中搜索匹配的条目。
  • 如果当前PC程序计数器在条目指定的范围内,并且抛出的异常类是条目指定的异常类(或者是指定异常类的子类),则异常表条目匹配。
  • Java 虚拟机按照条目在表中出现的顺序搜索异常表。当找到第一个匹配项时,Java 虚拟机将程序计数器设置为新的 pc 偏移位置并在那里继续执行。如果未找到匹配项,Java 虚拟机将弹出当前堆栈帧并重新抛出相同的异常。

JVM对异常表的约定

在 JVM 规范中,对 Exception table 有以下几个约定:
1.Exception table 的结构:
在 JVM 规范中,Exception table 被定义为一张表格,由多行记录组成。每一行记录用于描述一个代码块,其中包含了代码块的起始地址、结束地址、异常处理程序的程序计数器(PC)值以及异常类类型。

2.Exception table 的字节码偏移值:
在 JVM 规范中,Exception table 中的每个记录都包含了字节码偏移值(Bytecode offset)和行号信息,这些信息用于告诉 JVM 在哪个字节码偏移值处发生了异常。这一信息在调试 Java 代码时十分有用。

3.Exception table 列表的顺序:
在 JVM 规范中,Exception table 中的代码块记录必须按照字节码地址从低到高排序。这个顺序确保了在 JVM 查找代码块和异常处理程序时能够正确有效。

4.Exception table 的匹配逻辑:
在 JVM 规范中,当 JVM 发生异常时,会遍历 Exception table 列表,按行依次匹配当前 PC 计数器和代码块的起始和结束字节码偏移值,以确定当前发生异常所在的代码块和异常处理程序。

Exception table 是 JVM 中非常重要的数据结构之一,为 JVM 提供了一种有效、可靠的异常处理机制。在 Java 编程中,Exception table 可以帮助开发者更好更快地调试和解决问题,保证 Java 程序的可靠性和稳定性。

模拟JVM的执行过程

class Ball extends Exception {
}
class Pitcher {
    private static Ball ball = new Ball();
    static void playBall() {
        int i = 0;
        while (true) {
            try {
                if (i % 4 == 3) {
                    throw ball;
                }
                ++i;
            }
            catch (Ball b) {
                i = 0;
            }
        }
    }
}

编译后通过javap -v进行反编译,找到playBall Java方法对应的Code指令:

   0 iconst_0             // Push constant 0
   1 istore_0             // Pop into local var 0: int i = 0;
                          // The try block starts here (see exception table, below).
   2 iload_0              // Push local var 0
   3 iconst_4             // Push constant 4
   4 irem                 // Calc remainder of top two operands
   5 iconst_3             // Push constant 3
   6 if_icmpne 13         // Jump if remainder not equal to 3: if (i % 4 == 3) {
                          // Push the static field at constant pool location #5,
                          // which is the Ball exception itching to be thrown
   9 getstatic #5 <Field Pitcher.ball LBall;>
  12 athrow               // Heave it home: throw ball;
  13 iinc 0 1             // Increment the int at local var 0 by 1: ++i;
                          // The try block ends here (see exception table, below).
  16 goto 2               // jump always back to 2: while (true) {}
                          // The following bytecodes implement the catch clause:
  19 pop                  // Pop the exception reference because it is unused
  20 iconst_0             // Push constant 0
  21 istore_0             // Pop into local var 0: i = 0;
  22 goto 2               // Jump always back to 2: while (true) {}
Exception table:
   from   to  target type
     2    16    19   <Class Ball>

对于每个 catch 块捕获的异常,异常表都有一个条目。每个条目有四个信息:

  • from:可能发生异常的起始点指令索引下标(包含)
  • to:可能发生异常的结束点指令索引下标(不包含)
  • target:在from和to的范围内,发生异常后,开始处理异常的指令索引下标
  • type:当前范围可以处理的异常类信息

基于异常表条目,可以判断出

  • try块,对应PC偏移范围的 2~15
  • catch块,对应PC偏移范围的 19~21

异常表中,覆盖范围区间是左开右闭 [from, to),为什么没有包含右边界,这个就有点意思了

The fact that end_pc is exclusive is a historical mistake in the design of the Java Virtual Machine: if the Java Virtual Machine code for a method is exactly 65535 bytes long and ends with an instruction that is 1 byte long, then that instruction cannot be protected by an exception handler. A compiler writer can work around this bug by limiting the maximum size of the generated Java Virtual Machine code for any method, instance initialization method, or static initializer (the size of any code array) to 65534 bytes.
start_pc、end_pc 是一对参数,对应的是 Exception table 里面的 from 和 to,表示异常的覆盖范围。

不包含 end_pc 是 JVM 设计过程中的一个历史性的错误。

在Java中,一个方法的长度从字节码层面来说是有限制的。具体来说,Java虚拟机规范定义了一个方法的字节码长度不能超过65535个字节,也就是64KB。
这个限制是由于Java虚拟机规范中方法表结构的设计所决定的。方法表结构中有一个字段code_length用于表示方法的字节码长度,这个字段是一个16位的无符号整数,因此最大值为65535。

因为如果 JVM 中一个方法编译后的代码正好是 65535 字节长,并且以一条 1 字节长的指令结束,那么该指令就不能被异常处理机制所保护。存在边界问题,因此异常的覆盖范围定为左开右闭。

示例中,Pitcher#playball方法会一直循环;每经过四次循环,playball 就会抛出Ball并catch住。
因为 try 块和 catch 子句都在while(true) 循环中,所以永远不会停止。

局部变量i从 0 开始,每次循环递增。当if语句为 时true,即每次i等于 3 时都会抛出异常。
Java 虚拟机检查异常表,发现确实有匹配的条目。条目的有效范围是从 2 到 15,包括了在 pc 偏移量 12 处抛出异常。条目对应能处理的异常类型class 是Ball,抛出异常的 class 也是Ball。鉴于这种完美匹配,Java 虚拟机将抛出的异常对象压入堆栈,并在 pc 偏移量 19 处继续执行。
catch 子句只是将int i重置为 0,然后循环重新开始。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,874评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,151评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,270评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,137评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,116评论 5 370
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,935评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,261评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,895评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,342评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,854评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,978评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,609评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,181评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,182评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,402评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,376评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,677评论 2 344

推荐阅读更多精彩内容