JVM——JVM 是如何处理异常的?

JVM 是如何处理异常的?

在 Java 编程语言中,异常处理是一种强大的机制,用于应对程序运行时出现的错误和意外情况。而 Java 虚拟机(JVM)作为 Java 程序运行的核心环境,在异常处理过程中扮演着至关重要的角色。下面我们深入探讨 JVM 是如何处理异常的,从异常的基本概念、抛出与捕获机制、异常处理的性能影响,到 Java 7 引入的新特性等多个方面,进行全面而详细的剖析。

异常的基本概念

在 Java 语言规范中,所有异常都是 Throwable 类或者其子类的实例。Throwable 类有两个直接子类:ErrorException

  • Error :表示程序不应捕获的异常。当程序触发 Error 时,通常意味着程序的执行状态已经无法恢复,需要中止线程甚至是中止虚拟机。例如,OutOfMemoryError 表示内存溢出错误,VirtualMachineError 表示虚拟机错误等。这些错误往往是由系统级问题或资源耗尽等问题引起的,应用程序一般无法对其进行有效的处理。

  • Exception :涵盖程序可能需要捕获并且处理的异常。Exception 类又可以分为 RuntimeException 和其他类型的异常(即检查异常)。

RuntimeException 用来表示 “程序虽然无法继续执行,但是还能抢救一下” 的情况,如 ArrayIndexOutOfBoundsException(数组索引越界异常)、NullPointerException(空指针异常)等。RuntimeExceptionError 属于 Java 里的非检查异常(unchecked exception),而其他异常则属于检查异常(checked exception)。

在 Java 语法中,所有的检查异常都需要程序显式地捕获,或者在方法声明中用 throws 关键字标注。通常情况下,程序中自定义的异常应为检查异常,以便最大化利用 Java 编译器的编译时检查。这种检查机制可以在编译阶段帮助开发者发现潜在的异常处理问题,提高程序的健壮性。

异常的抛出与捕获机制

(一)抛出异常

抛出异常可分为显式和隐式两种。

  • 显式抛异常 :主体是应用程序,指的是在程序中使用 “throw” 关键字,手动将异常实例抛出。例如下面代码中,当年龄为负数时,程序显式地抛出一个 IllegalArgumentException 异常,提示年龄不能为负数。​

    if (age < 0) {throw new IllegalArgumentException("年龄不能为负数");
    }
  • 隐式抛异常 :主体则是 Java 虚拟机,它指的是 Java 虚拟机在执行过程中,碰到无法继续执行的异常状态,自动抛出异常。例如,Java 虚拟机在执行读取数组操作时,发现输入的索引值是负数,故而抛出数组索引越界异常(ArrayIndexOutOfBoundsException):  

     int[] arr = new int[5]; int value = arr[-1]; // 隐式抛出 ArrayIndexOutOfBoundsException

(二)捕获异常

捕获异常涉及如下三种代码块:

  • try 代码块 :用来标记需要进行异常监控的代码。开发者将可能抛出异常的代码放在 try 块中,以便 JVM 对其进行监控。

  • catch 代码块 :跟在 try 代码块之后,用来捕获在 try 代码块中触发的某种指定类型的异常。除了声明所捕获异常的类型之外,catch 代码块还定义了针对该异常类型的异常处理器。在 Java 中,try 代码块后面可以跟着多个 catch 代码块,来捕获不同类型的异常。Java 虚拟机会从上至下匹配异常处理器。因此,前面的 catch 代码块所捕获的异常类型不能覆盖后边的,否则编译器会报错。例如下面例子中,如果 try 块中的代码抛出了 IOException,则会被第一个 catch 块捕获并处理;如果抛出了其他类型的异常(如 NullPointerException 等),则会被第二个 catch 块捕获并处理。​

    try {// 可能抛出多种异常的代码
    } catch (IOException e) {// 处理 IOException 异常
    } catch (Exception e) {// 处理其他类型的异常
    }
  • finally 代码块 :跟在 try 代码块和 catch 代码块之后,用来声明一段必定运行的代码。它的设计初衷是为了避免跳过某些关键的清理代码,例如关闭已打开的系统资源。在程序正常执行的情况下,这段代码会在 try 代码块之后运行。否则,也就是 try 代码块触发异常的情况下:  

    • 如果该异常没有被捕获,finally 代码块会直接运行,并且在运行之后重新抛出该异常。  

    • 如果该异常被 catch 代码块捕获,finally 代码块则在 catch 代码块之后运行。在某些不幸的情况下,catch 代码块也触发了异常,那么 finally 代码块同样会运行,并会抛出 catch 代码块触发的异常。在某些极端不幸的情况下,finally 代码块也触发了异常,那么只好中断当前 finally 代码块的执行,并往外抛异常。 ​

JVM 如何捕获异常

在编译生成的字节码中,每个方法都附带一个异常表。异常表中的每一个条目代表一个异常处理器,并且由 from 指针、to 指针、target 指针以及所捕获的异常类型构成。这些指针的值是字节码索引(bytecode index,bci),用以定位字节码。

其中,from 指针和 to 指针标示了该异常处理器所监控的范围,例如 try 代码块所覆盖的范围。target 指针则指向异常处理器的起始位置,例如 catch 代码块的起始位置。

举个例子,在以下代码中:

public static void main(String[] args) {try {mayThrowException();} catch (Exception e) {e.printStackTrace();}
}

编译过后,该方法的异常表拥有一个条目。其 from 指针和 to 指针分别为 0 和 3,代表它的监控范围从索引为 0 的字节码开始,到索引为 3 的字节码结束(不包括 3)。该条目的 target 指针是 6,代表这个异常处理器从索引为 6 的字节码开始。条目的最后一列,代表该异常处理器所捕获的异常类型正是 Exception

当程序触发异常时,Java 虚拟机会从上至下遍历异常表中的所有条目。当触发异常的字节码的索引值在某个异常表条目的监控范围内,Java 虚拟机会判断所抛出的异常和该条目想要捕获的异常是否匹配。如果匹配,Java 虚拟机会将控制流转移至该条目 target 指针指向的字节码。

如果遍历完所有异常表条目,Java 虚拟机仍未匹配到异常处理器,那么它会弹出当前方法对应的 Java 栈帧,并且在调用者(caller)中重复上述操作。在最坏情况下,Java 虚拟机需要遍历当前线程 Java 栈上所有方法的异常表。

异常处理的性能影响

异常实例的构造十分昂贵。这是由于在构造异常实例时,Java 虚拟机便需要生成该异常的栈轨迹(stack trace)。该操作会逐一访问当前线程的 Java 栈帧,并且记录下各种调试信息,包括栈帧所指向方法的名字,方法所在的类名、文件名,以及在代码中的第几行触发该异常。

当然,在生成栈轨迹时,Java 虚拟机会忽略掉异常构造器以及填充栈帧的 Java 方法(Throwable.fillInStackTrace),直接从新建异常位置开始算起。此外,Java 虚拟机还会忽略标记为不可见的 Java 方法栈帧。

既然异常实例的构造十分昂贵,那么在实践中,我们应尽量避免频繁抛出和捕获异常,以免对程序性能造成较大影响。例如,在循环中抛出和捕获异常可能会导致程序运行缓慢。

Java 7 的新特性

(一)Supressed 异常

Java 7 引入了 Supressed 异常来解决异常链问题。这个新特性允许开发人员将一个异常附于另一个异常之上。因此,抛出的异常可以附带多个异常的信息。

然而,Java 层面的 finally 代码块缺少指向所捕获异常的引用,所以这个新特性使用起来非常繁琐。为此,Java 7 专门构造了一个名为 try-with-resources 的语法糖,在字节码层面自动使用 Supressed 异常。

(二)try-with-resources

try-with-resources 语法糖的主要目的是精简资源打开关闭的用法。在 Java 7 之前,对于打开的资源,我们需要定义一个 finally 代码块,来确保该资源在正常或者异常执行状况下都能关闭。资源的关闭操作本身容易触发异常。因此,如果同时打开多个资源,那么每一个资源都要对应一个独立的 try-finally 代码块,以保证每个资源都能够关闭。这样一来,代码将会变得十分繁琐。

Java 7 的 try-with-resources 语法糖极大地简化了上述代码。程序可以在 try 关键字后声明并实例化实现了 AutoCloseable 接口的类,编译器将自动添加对应的 close() 操作。在声明多个 AutoCloseable 实例的情况下,编译生成的字节码类似于上面手工编写代码的编译结果。与手工代码相比,try-with-resources 还会使用 Supressed 异常的功能,来避免原异常 “被消失”。

例如:

public class Foo implements AutoCloseable {private final String name;public Foo(String name) { this.name = name; }@Overridepublic void close() {throw new RuntimeException(name);}public static void main(String[] args) {try (Foo foo0 = new Foo("Foo0");Foo foo1 = new Foo("Foo1");Foo foo2 = new Foo("Foo2")) {throw new RuntimeException("Initial");}}
}

运行结果:

Exception in thread "main" java.lang.RuntimeException: Initialat Foo.main(Foo.java:18)Suppressed: java.lang.RuntimeException: Foo2at Foo.close(Foo.java:13)at Foo.main(Foo.java:19)Suppressed: java.lang.RuntimeException: Foo1at Foo.close(Foo.java:13)at Foo.main(Foo.java:19)Suppressed: java.lang.RuntimeException: Foo0at Foo.close(Foo.java:13)at Foo.main(Foo.java:19)

(三)多异常捕获

Java 7 还支持在同一 catch 代码块中捕获多种异常。实际实现非常简单,生成多个异常表条目即可。例如:

try {// 可能抛出多种异常的代码
} catch (IOException | SQLException e) {// 处理多种异常
}

实践分析

为了更好地理解 JVM 如何处理异常,我们可以进行一些实践分析。例如,查看以下代码:

public class Foo {private int tryBlock;private int catchBlock;private int finallyBlock;private int methodExit;public void test() {for (int i = 0; i < 100; i++) {try {tryBlock = 0;if (i < 50) {continue;} else if (i < 80) {break;} else {return;}} catch (Exception e) {catchBlock = 1;} finally {finallyBlock = 2;}}methodExit = 3;}
}

我们可以使用 javap -c 命令查看编译后的字节码,分析异常处理的机制。通过观察字节码,我们可以更深入地了解 JVM 如何处理 try-catch-finally 代码块,以及异常表条目的生成和匹配过程。

总结

本文详细探讨了 JVM 是如何处理异常的,包括异常的基本概念、抛出与捕获机制、异常处理的性能影响,以及 Java 7 引入的新特性等内容。通过深入理解这些知识,开发者可以在实际开发中更加合理地使用异常处理机制,提高程序的健壮性和性能。

在实际开发中,我们应尽量遵循以下原则:

  • 避免滥用异常来控制流程,因为异常处理机制相对耗时。

  • 合理使用检查异常和非检查异常,根据实际情况判断是否需要显式捕获或声明抛出异常。

  • 善用 Java 7 的新特性,如 try-with-resources 和多异常捕获,简化代码并提高异常处理的效率。

掌握 JVM 的异常处理机制对于 Java 开发者来说至关重要,它有助于我们编写出更高质量、更可靠的 Java 程序。

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

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

相关文章

MYSQL三大日志、隔离级别(MVCC+锁机制实现)

MySQL三大日志 ​Undo Log&#xff08;回滚日志&#xff09; 作用 事务回滚时恢复数据到修改前的状态。 支持 ​​MVCC​​&#xff0c;为读操作提供历史版本数据。 存储 存放在 undo tablespace 中&#xff0c;通过回滚段管理。 格式 undo log 格式都有一个 roll_point…

访问计划(C++)

题目描述 Farmer John 计划建造 N&#xff08;1≤N≤10^5&#xff09;个农场&#xff0c;用 N−1 条道路连接&#xff0c;构成一棵树&#xff08;也就是说&#xff0c;所有农场之间都互相可以到达&#xff0c;并且没有环&#xff09;。每个农场有一头奶牛&#xff0c;品种为更…

时间同步服务

时间同步:多主机协作工作时&#xff0c;各个主机的时间同步很重要&#xff0c;时间不一致会造成很多重要应用的故障&#xff0c;如:加密协议&#xff0c;日志&#xff0c;集群等&#xff0c;利用NTP(Network Time Protocol )协议使网络中的各个计算机 时间达到同步。目前NTP协议…

Cordova开发自定义插件的方法

Cordova开发自定义插件的方法 文章目录 Cordova开发自定义插件的方法[TOC](文章目录) 一、自定义插件二、android下的自定义插件开发&#xff08;一&#xff09;步骤1、建立cordova工程2、建立自定义插件&#xff08;1&#xff09; 安装plugman&#xff08;2&#xff09; 用plu…

【libm】2整数接口(int_traits.rs)

一、源码 int_traits.rs文件定义了两个核心 trait MinInt 和 Int&#xff0c;为整数类型提供统一的抽象接口&#xff0c;并通过宏为所有原生整数类型&#xff08;i8 ~ i128/u8 ~ u128&#xff09;实现这些 trait。 use core::{cmp, fmt, ops};/// Minimal integer implementa…

WebSocket实战经验

WebSocket实战经验详解 WebSocket基础概念 #mermaid-svg-sdkZP4UrWBpk2Hco {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-sdkZP4UrWBpk2Hco .error-icon{fill:#552222;}#mermaid-svg-sdkZP4UrWBpk2Hco .error-tex…

【C/C++】MQTT

文章目录 MQTT 协议1 基本概念2 核心特性3 核心组件4 C 简易实现&#xff08;基于 Paho MQTT 库&#xff09;环境准备示例代码 不同mqtt对比关键差异说明 MQTT 协议 1 基本概念 MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;是一种轻量级的发布/订阅模式…

《Java 高并发程序设计》笔记

&#x1f4a1; 根据 遗忘曲线&#xff1a;如果没有记录和回顾&#xff0c;6天后便会忘记75%的内容 读书笔记正是帮助你记录和回顾的工具&#xff0c;不必拘泥于形式&#xff0c;其核心是&#xff1a;记录、翻看、思考 ::: 书名Java 高并发程序设计作者葛一鸣、郭超状态已读完简…

Fine Structure-Aware Sampling(AAAI 2024)论文笔记和启发

文章目录 本文解决的问题本文提出的方法以及启发 本文解决的问题 传统的基于Pifu的人体三维重建一般通过采样来进行学习。一般选择的采样方法是空间采样&#xff0c;具体是在surface的表面随机位移进行样本的生成。这里的采样是同时要在XYZ三个方向上进行。所以这导致了一个问…

【AI面试准备】性能测试与AI模型结合应用指南

面试题&#xff1a; 性能测试&#xff1a;AI模型预测系统瓶颈&#xff08;如LoadRunnerAI模块&#xff09;。 性能测试与AI模型预测系统瓶颈的结合是当前软件工程和运维领域的重要趋势&#xff0c;能够显著提升系统优化效率和问题预测能力。以下从核心概念、技术实现、快速掌握…

Spring MVC 与 FreeMarker 整合

以下是 Spring MVC 与 FreeMarker 整合的详细步骤&#xff0c;包含配置和代码示例&#xff1a; 1. 添加依赖 在 pom.xml 中引入 Spring MVC 和 FreeMarker 的依赖&#xff08;以 Maven 为例&#xff09;&#xff1a; <!-- Spring Web MVC --> <dependency><gr…

Redis分布式锁使用以及对接支付宝,paypal,strip跨境支付

本章重点在于如何使用redis的分布式锁来锁定库存。减少超卖&#xff0c;同时也对接了支付宝&#xff0c;paypal&#xff0c;strip跨境支付 第一步先建立一个商品表 CREATE TABLE sys_product (id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键,code varchar(60) DEFAUL…

使用frpc链接内网的mysql

以下是配置 frpc 连接内网 MySQL 服务的详细步骤&#xff1a; 1. 准备工作 frps 服务器&#xff1a;已部署在公网 IP 11.117.11.245&#xff0c;假设 frps 的默认端口为 7000。 内网 MySQL 服务&#xff1a;运行在内网机器的 3306 端口。 目标&#xff1a;通过公网 IP 11.117…

2025信息安全网络安全意识培训资料汇编(24份)

最新整理&#xff1a;2025信息安全网络安全意识培训资料汇编&#xff0c;共24份资料&#xff0c;供学习参考。 互联网信息安全意识培训.pptx100个网络安全风险防范知识.pptx亚信信息安全意识培训.pptx网络安全法规及意识培训.pptx网络安全意识与案例分析.pptx绿盟-安全意识培训…

JAVA:使用 XStream 实现对象与XML转换的技术指南

1、简述 XStream 是一个简单便捷的 Java 库,用于对象与 XML 的相互转换。其主要特点是: 易于使用:无需复杂的配置即可直接使用。支持自定义:可以灵活地定制对象的序列化和反序列化规则。强大的功能:支持注解、自定义转换器等。本文将详细介绍 XStream 的基本使用方法,并…

VITA STANDARDS LIST,VITA 标准清单下载

VITA STANDARDS LIST&#xff0c;VITA 标准清单下载 DesignationTitleAbstractStatusVMEbus Handbook, 4th EditionA users guide to the VME, VME64 and VME64x bus specifications - features over 70 product photos and over 160 circuit diagrams, tables and graphs. The…

Assetto Corsa 神力科莎 [DLC 解锁] [Steam] [Windows]

Assetto Corsa 神力科莎 [DLC 解锁] [Steam] [Windows] 需要有游戏正版基础本体&#xff0c;安装路径不能带有中文&#xff0c;或其它非常规拉丁字符&#xff1b; DLC 版本 至最新全部 DLC 后续可能无法及时更新文章&#xff0c;具体最新版本见下载文件说明 DLC 解锁列表&…

【Java idea配置】

IntelliJ IDEA创建类时自动生成注释 /** * program: ${PROJECT_NAME} * * since: jdk1.8 * * description: ${description} * * author: ${USER} * * create: ${YEAR}-${MONTH}-${DAY} ${HOUR}:${MINUTE} **/自动导入和自动移除无用导入 idea彩色日志不生效 调试日志输出 在…

计算方法实验六 数值积分

【实验性质】综合性实验。 【实验目的】理解插值型积分法&#xff1b;掌握复化积分法算法。 【实验内容】 1对 &#xff0c;用复化梯形积分和变步长梯形积分求值&#xff08;截断误差不超过&#xff09;。 【理论基础】 积分在工程中有重要的应用&#xff0c;数值积分…

Webug4.0靶场通关笔记11- 第15关任意文件下载与第16关MySQL配置文件下载

目录 一、文件下载 二、第15关 任意文件下载 1.打开靶场 2.源码分析 3.渗透实战 三、第16关 MySQL配置文件下载 1.打开靶场 2.源码分析 3.渗透实战 &#xff08;1&#xff09;Windows系统 &#xff08;2&#xff09;Linux系统 四、渗透防御 一、文件下载 本文通过…