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
本文来自:“掘金”,了解相关信息可以关注“掘金”

相关文章
|
3月前
|
监控 数据可视化 Java
jvm性能调优实战 - 31从测试到上线_如何分析JVM运行状况及合理优化
jvm性能调优实战 - 31从测试到上线_如何分析JVM运行状况及合理优化
53 1
|
9天前
|
存储 XML 监控
JVM工作原理与实战(三):字节码文件的组成
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了字节码文件的基础信息、常量池、方法、字段、属性等内容。
26 6
|
2月前
|
存储 监控 Java
JVM内存泄漏的分析与解决方案
JVM内存泄漏的分析与解决方案
|
2月前
|
存储 监控 Java
JVM监控和分析技术在实践中可能会面临什么?
JVM监控和分析技术在实践中可能会面临什么?
|
6月前
|
存储 自然语言处理 Java
JVM第二讲:JVM 基础 - 字节码详解
JVM第二讲:JVM 基础 - 字节码详解
|
2月前
|
存储 Java 编译器
深入理解JVM - 字节码指令
深入理解JVM - 字节码指令
63 0
|
3月前
|
运维 监控 Java
【深入浅出JVM原理及调优】「搭建理论知识框架」全方位带你深度剖析Java线程转储分析的开发指南
学习JVM需要一定的编程经验和计算机基础知识,适用于从事Java开发、系统架构设计、性能优化、研究学习等领域的专业人士和技术爱好者。
55 5
【深入浅出JVM原理及调优】「搭建理论知识框架」全方位带你深度剖析Java线程转储分析的开发指南
|
3月前
|
存储 SQL Java
jvm性能调优实战 - 27亿级数据量的实时分析引擎,为啥频繁发生Full GC
jvm性能调优实战 - 27亿级数据量的实时分析引擎,为啥频繁发生Full GC
45 0
|
3月前
|
存储 Java
jvm性能调优实战 - 23 模拟Young GC的发生及分析GC日志
jvm性能调优实战 - 23 模拟Young GC的发生及分析GC日志
44 0
|
3月前
|
存储 算法 Java
JVM-01Java内存区域与内存溢出异常(上)【运行时区域数据】
JVM-01Java内存区域与内存溢出异常(上)【运行时区域数据】
36 0