异常是怎么被处理的?这题的答案不在源码里面。 (上)

简介: 异常是怎么被处理的?这题的答案不在源码里面。 (上)

image.png

他是看了我《神了!异常信息突然就没了?》这篇文章后产生的疑问。

既然是看了我的文章带来的进一步思考,恰巧呢,我又刚好知道。

虽然这类文章看的人少,但是我还是来填个坑。

害,真是暖男石锤了。


image.png


异常怎么被抛出的。


先上一个简单代码片段:

image.png

运行结果大家都是非常的熟悉。

光看这仅有的几行代码,我们是探索不出来什么有价值的东西。

我们都知道运行结果是这样的,没有任何毛病。

这是知其然。

那么所以然呢?

所以然,就藏在代码背后的字节码里面。

通过 javap 编译之后,上面的代码的字节码是这样:

image.png

我们主要关注下面部分,字节码指令对应的含义我也在后面注释一下:

public static void main(java.lang.String[]);
    Code:
       0: iconst_1 //将int型的1推送至栈顶
       1: iconst_0 //将int型的0推送至栈顶
       2: idiv     //将栈顶两int型数值相除并将结果压入栈顶
       3: istore_1 //将栈顶int型数值存入第二个本地变量
       4: return   //从当前方法返回 void

别问我怎么知道字节码的含义的,翻表就行了,这玩意谁背得住啊。

通过字节码,好像也没看出什么玄机来。

但是,你先记着这个样子,马上我给你表演一个变形:

public class MainTest {
    public static void main(String[] args) {
        try {
            int a = 1 / 0;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

用 try-catch 把代码包裹起来,捕获一下异常。

再次用 javap 编译之后,字节码变成了这个样子:

可以明显的看到,字节码发生了变化,至少它变长了。

主要还是关注我框起来的部分。

把两种情况的字节码拿来做个对比:

对比一下就很清楚了,加入 try-catch 之后,原有的字节码指令一行不少。

没有被框起来的,就是多出来的字节码指令。

而多出来的这部分,其中有个叫做 Exception table 尤为明显

image.png

异常表,这个玩意,就是 JVM 拿来处理异常的。

至于这里每个参数的含义是什么,我们直接绕过网上的“二手”资料,到官网上找文档:

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.3


image.png

看起来英文很多,很有压力,但是不要怕,有我呢,我挑关键的给你 say:


image.png


首先 start_pc、end_pc 是一对参数,对应的是 Exception table 里面的 from 和 to,表示异常的覆盖范围。

比如前面的 from 是 0 ,to 是 4,代表的异常覆盖的字节码索引就是这个范围:

0: iconst_1 //将int型的1推送至栈顶
1: iconst_0 //将int型的0推送至栈顶
2: idiv     //将栈顶两int型数值相除并将结果压入栈顶
3: istore_1 //将栈顶int型数值存入第二个本地变量

有个细节,不知道你注意到了没有。

范围不包含 4,范围区间是这样的 [start_pc, end_pc)。

而至于为什么没有包含 end_pc,这个就有点意思了。

拿出来讲讲。

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.

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

因为如果 JVM 中一个方法编译后的代码正好是 65535 字节长,并且以一条 1 字节长的指令结束,那么该指令就不能被异常处理机制所保护。

编译器作者可以通过限制任何方法、实例初始化方法或静态初始化器生成的代码的最大长度来解决这个错误。

上面就是官网的解释,反正就是看的似懂非懂的。

没关系,跑个例子就知道了:

image.png

当我代码里面只有一个方法,且长度为 16391 行时,编译出来的字节码长度为 65532。

而通过前面的分析我们知道,一行 a=1/0 的代码,会被编译成 4 行字节码。

那么只要我再加一行代码,就会超出限制,这个时候再对代码进行编译,会出现什么问题呢?

看图:


image.png


直接编译失败,告诉你代码过长。

所以你现在知道了一个知识点:一个方法的长度,从字节码层面来说是有限制的。但是这个限制算是比较的大,正常人是写不出这样长度的代码的。

虽然这个知识点没啥卵用,但是要是你在工作中真的碰到了一个方法长度成千上万行,即使没有触发字节码长度限制,我也送你一个字:快跑。


image.png

接着说下一个参数 handler_pc,对应的是 Exception table 里面的 target。

其实它非常好理解,就是指异常处理程序开始的那条指令对应的索引。

比如这里的 target 是 7 ,对应的就是 astore_1 指令:

image.png



目录
相关文章
|
2月前
|
存储
关于递归处理,应该怎么处理,思路是什么?
本文讨论了如何使用递归处理将嵌套对象中的所有名称(name)属性转换为标签(label)属性的问题,提供了详细的递归函数实现思路和代码示例。
29 0
关于递归处理,应该怎么处理,思路是什么?
|
5月前
|
存储 算法 数据挖掘
【模拟面试问答】力扣165题:比较版本号(逐个比较与双指针法详解及模拟面试问答)
【模拟面试问答】力扣165题:比较版本号(逐个比较与双指针法详解及模拟面试问答)
|
前端开发 JavaScript
|
IDE 程序员 开发工具
一道面试题的最终答案
一道面试题的最终答案
116 1
|
存储 算法 C语言
想要去欺负Leetcode的这些年——第二次,看看咱们是怎么把数组玩转,把数列理解透彻的~
想要去欺负Leetcode的这些年——第二次,看看咱们是怎么把数组玩转,把数列理解透彻的~
144 0
想要去欺负Leetcode的这些年——第二次,看看咱们是怎么把数组玩转,把数列理解透彻的~
AnswerGame答案解析
AnswerGame答案解析
63 0
【蓝桥杯】求1+2+3+...+n的值。(特别注意)
【蓝桥杯】求1+2+3+...+n的值。(特别注意)
【蓝桥杯】求1+2+3+...+n的值。(特别注意)
|
Java
异常是怎么被处理的?这题的答案不在源码里面。 (下)
异常是怎么被处理的?这题的答案不在源码里面。 (下)
105 0
异常是怎么被处理的?这题的答案不在源码里面。 (下)
|
Java 编译器 索引
异常是怎么被处理的?这题的答案不在源码里面。 (中)
异常是怎么被处理的?这题的答案不在源码里面。 (中)
110 0
异常是怎么被处理的?这题的答案不在源码里面。 (中)