卷向字节码-Java异常到底是怎么被处理的?(上)

简介: 卷向字节码-Java异常到底是怎么被处理的?(上)

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 编译之后,字节码变成了这个样子:

image.png

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

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

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

image.png

对比一下就很清楚了,加入 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

也就是告诉 JVM,如果出异常了,请从这里开始处理。

最后,看 catch_type 参数,对应的是 Exception table 里面的 type。

这里就是程序捕获的异常。

比如我把程序修改为这样,捕获三种类型的异常:

image.png

那么编译后的字节码对应的异常表所能处理的 type 就变成了这三个:

image.png

别问,问就是语法规定。

具体是啥语法规定呢?

就在异常表的这个地方:

image.png

编译器会检查该类是否是 Throwable 或 Throwable 的子类。

关于 Throwable、Exception、Error、RuntimeException 就不细说了,生成一个继承关系图给大家看就行了:

image.png

目录
相关文章
|
24天前
|
Java
在 Java 中捕获和处理自定义异常的代码示例
本文提供了一个 Java 代码示例,展示了如何捕获和处理自定义异常。通过创建自定义异常类并使用 try-catch 语句,可以更灵活地处理程序中的错误情况。
48 1
|
27天前
|
Java
轻松上手Java字节码编辑:IDEA插件VisualClassBytes全方位解析
本插件VisualClassBytes可修改class字节码,包括class信息、字段信息、内部类,常量池和方法等。
77 6
|
24天前
|
Java
在 Java 中,如何自定义`NumberFormatException`异常
在Java中,自定义`NumberFormatException`异常可以通过继承`IllegalArgumentException`类并重写其构造方法来实现。自定义异常类可以添加额外的错误信息或行为,以便更精确地处理特定的数字格式转换错误。
30 1
|
25天前
|
IDE 前端开发 Java
怎样避免 Java 中的 NoSuchFieldError 异常
在Java中避免NoSuchFieldError异常的关键在于确保类路径下没有不同版本的类文件冲突,避免反射时使用不存在的字段,以及确保所有依赖库版本兼容。编译和运行时使用的类版本应保持一致。
60 7
|
26天前
|
Java 编译器
如何避免在 Java 中出现 NoSuchElementException 异常
在Java中,`NoSuchElementException`通常发生在使用迭代器、枚举或流等遍历集合时,尝试访问不存在的元素。为了避免该异常,可以在访问前检查是否有下一个元素(如使用`hasNext()`方法),或者使用`Optional`类处理可能为空的情况。正确管理集合边界和条件判断是关键。
52 6
|
29天前
|
Java
Java异常捕捉处理和错误处理
Java异常捕捉处理和错误处理
19 1
|
1月前
|
Java 编译器 开发者
Java异常处理的最佳实践,涵盖理解异常类体系、选择合适的异常类型、提供详细异常信息、合理使用try-catch和finally语句、使用try-with-resources、记录异常信息等方面
本文探讨了Java异常处理的最佳实践,涵盖理解异常类体系、选择合适的异常类型、提供详细异常信息、合理使用try-catch和finally语句、使用try-with-resources、记录异常信息等方面,帮助开发者提高代码质量和程序的健壮性。
49 2
|
1月前
|
Java
如何在 Java 中处理“Broken Pipe”异常
在Java中处理“Broken Pipe”异常,通常发生在网络通信中,如Socket编程时。该异常表示写入操作的另一端已关闭连接。解决方法包括:检查网络连接、设置超时、使用try-catch捕获异常并进行重试或关闭资源。
88 5
|
1月前
|
Java 数据格式 索引
使用 Java 字节码工具检查类文件完整性的原理是什么
Java字节码工具通过解析和分析类文件的字节码,检查其结构和内容是否符合Java虚拟机规范,确保类文件的完整性和合法性,防止恶意代码或损坏的类文件影响程序运行。
40 5
|
1月前
|
Java API Maven
如何使用 Java 字节码工具检查类文件的完整性
本文介绍如何利用Java字节码工具来检测类文件的完整性和有效性,确保类文件未被篡改或损坏,适用于开发和维护阶段的代码质量控制。
67 5