JVM 异常表及 try-catch-finally 字节码分析

简介: 云栖号资讯:【点击查看更多行业资讯】在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来! 作为一个“有经验”的 Java 工程师,你一定知道什么是try-catch-finally代码块。

云栖号资讯:【点击查看更多行业资讯
在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来!


作为一个“有经验”的 Java 工程师,你一定知道什么是try-catch-finally代码块。但是你知道 JVM 是如何处理异常的吗?今天我们就来讲讲异常在 JVM 中的处理机制,以及字节码中异常表。

希望在这之后,不会有人再将下面这张表情包发给你……

1

环境介绍

  • jdk 1.8.0_151
  • IntelliJ IDEA 及 jclasslib 插件

字节码中的 try-catch

Talk is cheap, show you my code!

反编译后的字节码

首先我编写了第一段测试代码,这里有一个 try-catch 代码块,每个代码块中都有一行输出,在 catch 代码块中捕获的是 Exception 异常。

1

然后在命令行中先定位到这个类的字节码文件目录中,执行主方法后敲下javap -c 类名进行反编译,或者直接在编译器中选择Build Project,然后打开 jclasslib 工具就可以看到这个类的字节码。

我选择了第二个方法,主方法的字节码如下图:

2

可以看到0~3行是 try 代码块中的输出语句,12~17行是 catch 代码块中的输出语句。然后重点来了。

第8行的字节码是8 goto 20,这是什么意思呢?没错,盲猜就能猜到,这个字节码指令就是跳转到第20行的意思。这一行是说,如果 try 代码块中没有出现异常,那么就跳转到第20行,也就是整个方法行完成后 return 了。

这是正常的代码执行流程,那么如果出现异常了,虚拟机是如何知道应该“监控” try 代码块?它又是怎么知道该捕获何种异常呢?

答案就是——异常表。

异常表

在一个类被编译成字节码之后,它的每个方法中都会有一张异常表。异常表中包含了“监控”的范围,“监控”何种异常以及抛出异常后去哪里处理。比如上述的示例代码,在 jclasslib 中它的异常表如下图。

3

或者在javap -c命令下异常表是这样的:

4

无论是哪种形式的异常表,我们可以知道的是,异常表中每一行就代表一个异常处理器。

  • Nr. 代表异常处理器的序号
  • Start PC (from),代表异常处理器所监控范围的起始点
  • End PC (to),代表异常处理器所监控范围的结束点(该行不被包括在监控范围内,一般是 goto 指令)
  • Handler PC (target),指向异常处理器的起始位置,在这里就是 catch 代码块的起始位置
  • Catch Type (type),代表异常处理器所捕获的异常类型

如果程序触发了异常,Java 虚拟机会按照序号遍历异常表,当触发的异常在这条异常处理器的监控范围内(from 和 to),且异常类型(type)与该异常处理器一致时,Java 虚拟机就会跳转到该异常处理器的起始位置(target)开始执行字节码。

如果程序没有触发异常,那么虚拟机会使用 goto 指令跳过 catch 代码块,执行 finally 语句或者方法返回。

字节码中的 finally

接下来在上述的代码中再加入一个 finally 代码块,然后再次执行反编译的命令看看有什么不一样。

2

3

finally 代码块在当前版本(jdk 1.8)的 JVM 中的处理机制是比较特殊的。从上面的字节码中也可以明显看到,只是加了一个 finally 代码块而已,字节码指令增加了很多行。

如果再仔细观察一下,我们可以发现,在字节码指令中,有三块重复的字节码指令,分别是8~13行、28~33行和40~45行,如果对字节码有些了解的同学或许已经知道了,这三块重复的字节码就是 finally 代码块对应的代码。

出现三块重复字节码指令的原因是在 JVM 中,所有异常路径(如try、catch)以及所有正常执行路径的出口都会被附加一份 finally 代码块。也就是说,在上述的示例代码中,try 代码块后面会跟着一份 finally 的代码,catch 代码块后面也是如此,再加上原本正常流程会执行的 finally 代码块,在字节码中一共有三份 finally 代码块代码块。

而针对每一条可能出现的异常的路径,JVM 都会在异常表中多生成一条异常处理器,用来监控整个 try-catch 代码块,同时它会捕获所有种类的异常,并且在执行完 finally 代码块之后会重新抛出刚刚捕获的异常。

上述示例代码的异常表如下

5

可以看到与原来相比异常表增加了两条,第2条异常处理器异常监控 try 代码块,第3条异常处理器监控 catch 代码块,如果出现异常则会跳转到第39行的 finally 代码块执行。

这就是 finally 一定会在 try-catch 代码块之后执行的原因了(某些能中断程序运行的操作除外)。

如果 finally 也抛出异常

上文说到虚拟机会对整个 try-catch 代码块生成一个或多个异常处理器,如果在 catch 代码块中抛出了异常,这个异常会被捕获,并且在执行完 finally 代码块之后被重新抛出。

那么在这里有一个额外的问题需要提及,假设在 catch 代码块中抛出了异常 A,当执行 finally 代码块时又抛出了异常 B,那么最后抛出的是什么异常呢?

如果有同学自己尝试过这个操作,就会知道最后抛出的异常 B。也就是说,在捕获了 catch 代码块中的异常后,如果 finally 代码块中也抛出了异常,那么最终将会抛出 finally 中抛出的异常,而原来 catch 代码块中的异常将会被忽略。

如果代码块中有 return

讲完了异常在各个代码块中的情况,接下来再来考虑一下 return 关键字吧,如果 try 或者 catch 中有 return,finally 还会执行吗?如果 finally 中也有 return,那么最终返回的值是什么?为了说明这个问题,我编写了一段测试代码,然后找到它的字节码指令。

6

正如上文所述,finally 代码块会在所有正常及异常的路径上都复制一份,在这段字节码中,iconst_3 就是对应着 finally 代码块,共三份,所以即便在 try 或者 catch 代码块中有 return 语句,最终还是会会执行 finally 代码块中的内容。

也就是说,这个方法最终的返回结果是3。

小结

  • try-catch 语句的字节码指令
  • 异常表的介绍及 JVM 中异常处理流程
  • JVM 中关于 finally 代码块的特殊处理
  • 关于 return 关键字在 try-catch-finally 中的说明

【云栖号在线课堂】每天都有产品技术专家分享!
课程地址:https://yqh.aliyun.com/live

立即加入社群,与专家面对面,及时了解课程最新动态!
【云栖号在线课堂 社群】https://c.tb.cn/F3.Z8gvnK

原文发布时间:2020-05-29
本文作者:Planeswalker23
本文来自:“掘金”,了解相关信息可以关注“掘金”

相关文章
|
Arthas 存储 算法
深入理解JVM,包含字节码文件,内存结构,垃圾回收,类的声明周期,类加载器
JVM全称是Java Virtual Machine-Java虚拟机JVM作用:本质上是一个运行在计算机上的程序,职责是运行Java字节码文件,编译为机器码交由计算机运行类的生命周期概述:类的生命周期描述了一个类加载,使用,卸载的整个过类的生命周期阶段:类的声明周期主要分为五个阶段:加载->连接->初始化->使用->卸载,其中连接中分为三个小阶段验证->准备->解析类加载器的定义:JVM提供类加载器给Java程序去获取类和接口字节码数据类加载器的作用:类加载器接受字节码文件。
1036 55
|
监控 Java Unix
6个Java 工具,轻松分析定位 JVM 问题 !
本文介绍了如何使用 JDK 自带工具查看和分析 JVM 的运行情况。通过编写一段测试代码(启动 10 个死循环线程,分配大量内存),结合常用工具如 `jps`、`jinfo`、`jstat`、`jstack`、`jvisualvm` 和 `jcmd` 等,详细展示了 JVM 参数配置、内存使用、线程状态及 GC 情况的监控方法。同时指出了一些常见问题,例如参数设置错误导致的内存异常,并通过实例说明了如何排查和解决。最后附上了官方文档链接,方便进一步学习。
3174 4
|
缓存 Java
《JVM由浅入深学习九】 2024-01-15》JVM由简入深学习提升分(生产项目内存飙升分析)
《JVM由浅入深学习九】 2024-01-15》JVM由简入深学习提升分(生产项目内存飙升分析)
225 0
|
Arthas 存储 Java
JVM深入原理(三+四):JVM组成和JVM字节码文件
目录3. JVM组成3.1. 组成-运行时数据区3.2. 组成-类加载器3.3. 组成-执行引擎3.4. 组成-本地接口4. JVM字节码文件4.1. 字节码文件-组成4.1.1. 组成-基础信息4.1.1.1. 基础信息-魔数4.1.1.2. 基础信息-主副版本号4.1.2. 组成-常量池4.1.3. 组成-方法4.1.3.1. 方法-工作流程4.1.4. 组成-字段4.1.5. 组成-属性4.2. 字节码文件-查看工具4.2.1. javap4.2.2. jclasslib4.2.3. 阿里Arthas
228 0
|
存储 安全 Java
JVM深入原理(五):JVM组成和JVM字节码文件
类的生命周期概述:类的生命周期描述了一个类加载,使用,卸载的整个过类的生命周期阶段:类的声明周期主要分为五个阶段:加载->连接->初始化->使用->卸载,其中连接中分为三个小阶段验证->准备->解析。
222 0
|
监控 算法 Java
jvm-48-java 变更导致压测应用性能下降,如何分析定位原因?
【11月更文挑战第17天】当JVM相关变更导致压测应用性能下降时,可通过检查变更内容(如JVM参数、Java版本、代码变更)、收集性能监控数据(使用JVM监控工具、应用性能监控工具、系统资源监控)、分析垃圾回收情况(GC日志分析、内存泄漏检查)、分析线程和锁(线程状态分析、锁竞争分析)及分析代码执行路径(使用代码性能分析工具、代码审查)等步骤来定位和解决问题。
381 6
|
小程序 Oracle Java
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
这篇文章是关于JVM基础知识的介绍,包括JVM的跨平台和跨语言特性、Class文件格式的详细解析,以及如何使用javap和jclasslib工具来分析Class文件。
459 0
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
jmap 查看jvm内存大小并进行dump文件内存分析
jmap 查看jvm内存大小并进行dump文件内存分析
955 3
|
运维 监控 Java
(十)JVM成神路之线上故障排查、性能监控工具分析及各线上问题排错实战
经过前述九章的JVM知识学习后,咱们对于JVM的整体知识体系已经有了全面的认知。但前面的章节中,更多的是停留在理论上进行阐述,而本章节中则更多的会分析JVM的实战操作。
823 1
|
Arthas 监控 Java
JVM内存问题之使用gperftools分析JNI Memory泄漏的具体步骤是什么
JVM内存问题之使用gperftools分析JNI Memory泄漏的具体步骤是什么
538 2