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

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

所以,上面的消息汇总一下:

  • 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

它的示例代码是这样的:


image.png

意思就是如果抛出的值与 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 编译后,异常表部分出现了三条记录:

image.png


第一条认识,是我们主动捕获的异常。

第二三条都是 any,这是啥玩意?

答案在这:

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-3.html#jvms-3.13

image.png


主要看我画线的地方:

一个带有 finally 子句的 try 语句被编译为有一个特殊的异常处理程序,这个异常处理程序可以处理在 try 语句中抛出的(any)任何异常。

所有,翻译一下上面的异常表就是:

  • 如果 0 到 4 的指令之间发生了 Exception 类型的异常,调用索引为 15 的指令,开始处理异常。
  • 如果 0 到 4 的指令之间,不论发生了什么异常,都调用索引为 31 的指令(finally 代码块开始的地方)
  • 如果 15 到 20 的指令之间(也就是 catch 的部分),不论发生了什么异常,都调用索引为 31 的指令。

接着,我们把目光放到这一部分:



image.png


怎么样,发现了没?就问你神不神奇?

在源码中,只在 finally 代码块出现过一次的输出语句,在字节码中出现了三次。

finally 代码块中的代码被复制了两份,分别放到了 try 和 catch 语句的后面。再配合异常表使用,就能达到 finally 语句一定会被执行的效果。

以后再也不怕面试官问你为什么 finally 一定会执行了。

虽然应该也没有面试官会问这样无聊的问题。

问起来了,就从字节码的角度给他分析一波。

当然了,如果你非要给我抬个杠,聊聊 System.exit 的情况,就没多大意义了。

最后,关于 finally,再讨论一下这个场景:

public class MainTest {
    public static void main(String[] args) {
        try {
            int a = 1 / 0;
        } finally {
            System.out.println("final");
        }
    }
}

这个场景下,没啥说的, try 里面抛出异常,触发 finally 的输出语句,然后接着被抛出去,打印在控制台:

image.png

如果我在 finally 里面加一个 return 呢?

可以看到,运行结果里面异常都没有被抛出来:


image.png


为什么呢?

答案就藏在字节码里面:


image.png


其实已经一目了然了。

右边的 finally 里面有 return,并没有 athrow 指令,所以异常根本就没有抛出去。

这也是为什么建议大家不要在 finally 语句里面写 return 的原因之一。

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