通过JVM深入理解Java异常机制

news2024/12/19 18:33:47

JVM内部结构

要深入理解JVM异常处理机制,需要从JVM内部结构开始。
下图描述的主要是Java程序在执行时,由JVM管理的运行时数据区;包括方法区、Java堆、Java虚拟机栈、PC寄存器、本地方法栈,还有常量池。它们又被分为两大类——线程共享和线程私有数据区。

  • 线程共享数据区包括:Java堆、方法区/永久代/元空间、常量池。它们会随着虚拟机启动而创建,随着虚拟机退出而销毁。
  • 线程私有数据区包括:PC寄存器、JVM栈、native本地方法栈。它们是与线程一一对应的,这些与线程对应的数据区域会随着线程开始和结束而创建和销毁。

而今天要聊的Java异常处理机制,跟线程私有的 JVM栈和PC寄存器有关。
JVM 用栈来跟踪一系列的方法调用过程。该堆栈保存了每个调用方法的信息。当一个新的方法被调用时,JVM把描述该方法的信息封装为栈帧置入栈顶,位于栈顶的方法为正在执行的方法。
每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在JVM栈中从入栈到出栈的过程。

如果在执行的方法过程中抛出异常,JVM必须找到能捕获处理该异常的catch块

  • JVM首先观察当前方法是否存在能够处理该异常的catch块,如果存在,就执行该catch代码块
  • 如果不存在,JVM会从从栈中弹出该方法的栈帧,继续到前一个方法中查找合适的catch块
  • 当JVM追溯到调用栈的最底部的方法时,如果仍然没有找到处理该异常的代码块,将调用异常对象的printStackTrace()方法,打印来自方法调用栈的异常信息随后终止整个应用程序。

Java异常表(Exception table)

提到JVM的异常处理机制,不得不提及Exception Table;以下称为异常表。
Java 异常表(Exception table)是 Java 编译器为每个方法生成的一张映射表,它用于记录方法中每个代码块(try-catch/try-finally 等)及其对应的异常信息。在 Java 方法执行时,如果发生异常,Java 虚拟机会根据异常表的信息查找并确定正确的异常处理程序。
每个通过try-catch捕获异常的方法,都有一个异常表与之关联;该表随方法的字节码序列一起在字节码文件中存放。对于 try 块捕获的每个异常,异常表都有一个条目。
如果在方法执行期间抛出异常,Java 虚拟机会在异常表中搜索匹配的条目。如果抛出异常代码行号(当前PC程序计数器)在条目指定的范围内,并且抛出的异常类是条目指定的异常类(或者是指定异常类的子类),则异常表条目匹配。

Java 虚拟机按照条目在表中出现的顺序搜索异常表。

  • 当找到第一个匹配项时,Java 虚拟机将PC寄存器(程序计数器)设置为新的 pc 偏移位置并在那里继续执行。
  • 如果未找到匹配项,Java 虚拟机将弹出当前堆栈帧并重新抛出相同的异常。当 Java 虚拟机弹出当前栈帧时,它有效地中止了当前方法的执行并返回到调用该方法的上一级方法。但是它并没有在上一级方法中继续正常执行,而是在那个方法的调用处抛出了同样的异常,这导致Java虚拟机经历了同样的过程,搜索那个方法的异常表。

Java字节码是通过javac编译器 基于Java源代码文件生成的,就是按照字节码规范重新将源代码换成JVM可理解的表达方式而已;
因此Java源代码中,使用try-catch块捕获异常的方法,生成字节码时,会通过异常表(Exception table)来描述源代码中的try-catch;具体来说,异常表中的每个条目,必须包含 try 块的起始位置和结束位置、catch 块中捕获的异常类型以及捕获异常后跳转执行的位置。

字节码文件中,方法Code里的Exception table,包含如下信息:

  • from 可能发生异常的起始点
  • to 可能发生异常的结束点
  • target 上述from和to之前发生异常后的异常处理器的位置
  • type 异常处理器(能)处理的异常类型

其中,from、to和target都是PC寄存器(程序计数器)所指示的字节码行号;程序的跳转执行,就是修改PC寄存器,改变下一行要执行的代码位置来实现的。

Java异常表实战

下面我们创建ExceptionTable类,编译后再通过javap反汇编字节码文件;

public class ExceptionTable {
    public static void main(String[] args) throws Exception {
        try {
            throw new Exception();
        } catch (Exception e) {
            System.out.println("Caught!");
        } finally {
            System.out.println("Finally!");
        }
    }
}

javap -v ExceptionTable.class

// 省略Constant pool
{
  public exception.ExceptionTable();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lexception/ExceptionTable;

  public static void main(java.lang.String[]) throws java.lang.Exception;
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: new           #2                  // class java/lang/Exception
         3: dup
         4: invokespecial #3                  // Method java/lang/Exception."<init>":()V
         7: athrow
         8: astore_1
         9: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
        12: ldc           #5                  // String Caught!
        14: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        17: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
        20: ldc           #7                  // String Finally!
        22: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        25: goto          39
        28: astore_2
        29: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
        32: ldc           #7                  // String Finally!
        34: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        37: aload_2
        38: athrow
        39: return
      Exception table:
         from    to  target type
             0     8     8   Class java/lang/Exception
             0    17    28   any
      LineNumberTable:
        line 6: 0
        line 7: 8
        line 8: 9
        line 10: 17
        line 11: 25
        line 10: 28
        line 12: 39
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            9       8     1     e   Ljava/lang/Exception;
            0      40     0  args   [Ljava/lang/String;
      StackMapTable: number_of_entries = 3
        frame_type = 72 /* same_locals_1_stack_item */
          stack = [ class java/lang/Exception ]
        frame_type = 83 /* same_locals_1_stack_item */
          stack = [ class java/lang/Throwable ]
        frame_type = 10 /* same */
    Exceptions:
      throws java.lang.Exception
}
SourceFile: "ExceptionTable.java"

main方法的Code指令后面,我们能看到Exception table:
异常表第一行:它是我们的try-catch语句,如果在字节码的0-8行发生异常,将跳转到第8行进行处理
异常表第二行:它是我们的finally语句,无论0-17行发生什么,最终都将由第28行进行处理。

异常表中的 any

  • 如果命中了 any 之后,会将异常继续向上抛出去,交由该方法的调用方法处理。

被冗余/重复的finally
仔细观察ExceptionTable#main方法的Code指令,会发现finally块的内容是重复的。
在 JDK1.4.2之前,javac 编译器使用 jsrret 指令来实现 finally 语句,但是JDK1.4.2之后自动在每段可能的分支路径后将 finally 语句块内容冗余生成一遍来实现。JDK1.7及之后版本,则完全禁止在字节码文件中使用 jsrret 指令。
冗余finally块的指令,就是把finally 代码块对应的指令复制一份,分别放到了 try/catch 指令的后面,就能达到 finally 一定会被执行的效果。

jsrret是早期Java字节码中的两个指令,用于实现Java方法的子程序调用和返回。

  • jsr指令(Jump Subroutine)用于实现Java方法的子程序调用。它的作用是将当前方法的返回地址压入操作数栈中,并跳转到指定的子程序执行。在子程序执行完毕后,使用ret指令返回到原来的方法,并将返回值压入操作数栈中。在 JDK1.4.2之前,JVM处理异常时,调用异常处理程序就是通过jsr。
  • ret指令(Return from Subroutine)用于实现Java方法的返回。它的作用是将操作数栈顶的值作为返回值返回,并跳转到之前使用jsr指令保存的返回地址处继续执行。

jsrret指令在Java SE 6及以前的版本中是合法的指令,但在Java SE 7中已经被废弃。在Java SE 7及以后的版本中,应该使用invokedynamic指令来实现动态语言支持,而不是使用jsr和ret指令。

总结

1.JVM异常处理流程

  • 当Java程序中发生异常时,JVM会在方法的异常表中查找相应的异常处理代码。如果找到了匹配的异常处理代码,JVM会执行该代码来处理异常;如果没有找到匹配的代码,JVM会将异常向上抛出,弹出当前方法的栈帧,返回到调用该方法的上一级方法,查找上一级方法的异常表;
  • 依次沿调用栈查找,如果所有的栈帧被弹出,仍然没有处理,则将异常抛给当前的Thread,Thread会终止;如果当前Thread为最后一个非守护线程,且未处理异常,则会导致JVM终止运行。

当 JVM 中的所有非守护线程都已经结束时,JVM 就会自动退出,而无论该线程是否抛出了未捕获的异常。但是,如果这个非守护线程抛出了未被处理的异常,JVM 会在退出之前将堆栈信息打印到控制台。此外,非守护线程如果没有设置 setUncaughtExceptionHandler,也会将未捕获的异常传递给默认的未捕获异常处理程序来进行处理,该处理程序简单地打印了异常的堆栈轨迹到控制台上。

编译器生成字节码指令时,通过冗余finally块指令内容,达到 finally 一定会被执行的效果。
2.Java异常表

  • 编译器生成的异常表,是按Java源代码中catch块声明的顺序依次列出每个异常条目;
  • 每个异常条目包含from、to、target和type;finally块对应的条目一定在最后一行,from~to的范围是整个try块,且type是any 代表一定会执行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/612496.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SeaTunnel StarRocks 连接器的使用及原理介绍

作者&#xff1a;毕博&#xff0c;马蜂窝数据平台负责人&#xff0c;StarRocks 活跃贡献者 & Apache SeaTunnel 贡献者 Apache SeaTunnel&#xff08;以下简称 SeaTunnel&#xff09;是一个分布式、高性能、易扩展、用于海量数据&#xff08;离线&实时&#xff09;同步…

Spring为什么默认是单例的?

目录 一、五种作用域 二、单例bean与原型bean的区别 三、单例Bean的优势与劣势 一、五种作用域 1.singleton: singleton是Spring Bean的默认作用域&#xff0c;也就是单例模式。在整个应用程序中&#xff0c;只会创建一个实例&#xff0c;Bean的所有请求都会共享这个实例。 …

ETLCloud轻松应对CDC实时数据流和维度数据合并的需求,实时监控订单数据

如何实现实时流与批流合并打宽数据 通常情况下我们使用CDC实时监听表销售或订单表数据的LOG时会形成流式的数据&#xff0c;即订单变化时数据是按照变化时间不断的传入到ETL的流程中的&#xff0c;业务希望实时看到订单数据的报表。 CDC每次传入的数据有可能是一条也可能是多…

基于geoserver开发地图发布服务

写在前面&#xff1a;我在github上创建了对应的项目&#xff0c;可点此跳转&#xff0c;本文的所有源码均可在项目里找到&#xff0c;欢迎大家访问交流 一、开发背景 在gis领域&#xff0c;geoserver是后端地图发布的开源项目。目前我们在启动服务后&#xff0c;可通过自带的…

科研工具-R-META分析与【文献计量分析、贝叶斯、机器学习等】多技术融合实践

Meta分析是针对某一科研问题&#xff0c;根据明确的搜索策略、选择筛选文献标准、采用严格的评价方法&#xff0c;对来源不同的研究成果进行收集、合并及定量统计分析的方法&#xff0c;最早出现于“循证医学”&#xff0c;现已广泛应用于农林生态&#xff0c;资源环境等方面。…

【AIGC】14、GLIPv2 | 在 GLIP 上扩展 negative phrase 并新增分割功能

文章目录 一、背景二、方法2.1 A Unified VL Formulation and Architecture2.2 GLIPv2 pre-training2.3 将 GLIPv2 迁移到 Localization 和 VL task 三、结果3.1 One model architecture for all3.2 One set of model parameters for all3.3 GLIPv2 as a strong few-shot learn…

Latex使用algorithm2e包写伪代码

用Latex写伪代码我们需要用到一个包&#xff0c;Algorithm2e&#xff0c;这个工具包的使用手册下载地址为&#xff08;http://mlg.ulb.ac.be/files/algorithm2e.pdf&#xff09;CSDN的链接为&#xff08;&#xff09; 准备 导入该包 \usepackage[ruled,linesnumbered]{algor…

上海亚商投顾:沪指小幅震荡微涨 AI应用端持续活跃

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 市场情绪 大小指数今日走势分化&#xff0c;沪指全天窄幅震荡&#xff0c;创业板指低开低走&#xff0c;盘中一度跌超1.6%&a…

【Java基础】I/O流 —— Java中的流都需要关闭吗?

目录 一、为什么要关闭流&#xff1f;二、close方法和flush方法1.使用close方法2.使用flush方法 三、流按指向分类四、不用关闭的流 一、为什么要关闭流&#xff1f; 涉及到对外部资源的读写操作&#xff0c;包括网络、硬盘等等的I/O流&#xff0c;如果在使用完毕之后不关闭&a…

Unity基础框架从0到1(六)对象池模块

索引 这是Unity基础框架从0到1的第六篇文章&#xff0c;框架系列的项目地址是&#xff1a;https://github.com/tang-xiaolong/SimpleGameFramework 文章最后有目前框架系列的思维导图&#xff0c;前面的文章和对应的视频我一起列到这里&#xff1a; 文章 Unity基础框架从0到…

算力不竭如江海,天翼云“息壤”如何助力千行百业算力智能调度?

科技云报道原创。 数字时代下&#xff0c;算力已成为新型生产力&#xff0c;并朝着多元泛在、安全可靠、绿色低碳的方向演进。以算力为核心的数字信息基础设施&#xff0c;是国家战略性布局的关键组成部分&#xff0c;也成为数字经济时代的“大国重器”。 作为云服务国家队&am…

报表生成器FastReport .Net教程:“Text“对象、文本编辑

FastReport .Net是一款全功能的Windows Forms、ASP.NET和MVC报表分析解决方案&#xff0c;使用FastReport .NET可以创建独立于应用程序的.NET报表&#xff0c;同时FastReport .Net支持中文、英语等14种语言&#xff0c;可以让你的产品保证真正的国际性。 FastReport.NET官方版…

es elasticsearch 十四 各种机制 评分机制 正序索引 解决跳跃结果问题 解决耗时过长问题 解决相同属性值都到一个地方

目录 评分机制 机制 查看评分实现如何算出来的explaintrue 分析能否被搜索到 Doc value 正排序索引 Query phase Fetch phase Preference 问题 解决跳跃结果问题 Timeout 到达时间直接返回&#xff0c;解决耗时过长问题 Routing 数据准确分配到某地&#xff0c;解决相…

这才叫软件测试工程师,你那最多是混口饭吃罢了....

前些天和大学室友小聚了一下&#xff0c;喝酒喝大发了&#xff0c;谈天谈地谈人生理想&#xff0c;也谈到了我们各自的发展&#xff0c;感触颇多。曾经找工作我迷茫过、徘徊不&#xff0c;毕业那会我屡屡面试失败&#xff0c;处处碰壁&#xff1b;工作两年后我一度想要升职加薪…

006+limou+C语言“堆的实现”与“树的相关概念”

0.前言 这里是limou3434的一篇个人博文&#xff0c;感兴趣可以看看我的其他内容。本次我给您带来的是树的相关只是&#xff0c;并且把堆这一数据结构做了实现&#xff0c;后面还有大量的oj题目。但是树重点也就在这十多道oj题目中&#xff0c;您可以尝试着自己做一下&#xff…

我的创作纪念日|写在CSDN创作第512天

机缘 今天无意中发现CSDN后台给我发送私信&#xff0c;才发觉原来我的第一篇博客更新已经过去512天了&#xff0c;512天一晃而过居然还有点恍然。 作为一名网络专业的在校大学生&#xff0c;最初开始查找相关的资料其实更习惯于从外站进行查找&#xff0c;却总是在不经意中进入…

人事管理项目-前端实现

人事管理项目-前端实现 引入Element和Axios开发Login页面配置路由配置请求转发启动前端项目 引入Element和Axios 前端UI使用Element&#xff0c;网络请求则使用Axios&#xff0c;因此首先安装Element和Axios依赖&#xff0c;代码如下&#xff1a; 依赖添加成功后&#xff0c;接…

N-propargyloxycarbonyl-L-lysine,1215204-46-8,是一种基于赖氨酸的非天然氨基酸 (UAA)

产品描述&#xff1a; N-ε-propargyloxycarbonyl-L-lysine (H-L-Lys(Poc)-OH) 是一种基于赖氨酸的非天然氨基酸 (UAA)。 广泛用于多种生物体中荧光探针的生物偶联。 N- ε- Propargyloxycarbonyl-L-lysine (H-L-Lys (Poc) - OH) is a non natural amino acid (UAA) based on …

Kotlin Channel系列(一)之读懂Channel每一行源码

文章目录 有话说概述初识ChannelChannel种类Channel五大金刚SendReceiveClosedQueueBuffer Channel的行为Channel源码分析发送数据大动脉接收数据大动脉父类默认实现方式(RendezvousChannel)发送流程send()函数onSend()函数 接收流程receiveCatching()函数onReceiveCatching()函…

基于图像处理的圆检测与深度学习

基于图像处理的圆检测与深度学习 摘 要一、 绪论二 、图像预处理2.1 滤波算法2.2 边缘检测 三 、圆识别与定位算法3.2 定位算法3.2.1 迭代算法 4.1 数据处理 五、深度学习介绍&#xff1a;参考文献 摘 要 本文主要论述在图像处理的的基础上&#xff0c;为了克服图像背景中的亮…