JVM总结-异常处理
众所周知,异常处理的两大组成要素是抛出异常和捕获异常。这两大要素共同实现程序控制流的非正常转移。
抛出异常可分为显式和隐式两种。
显式抛异常的主体是应用程序,它指的是在程序中使用“throw”关键字,手动将异常实例抛出。
隐式抛异常的主体则是 Java 虚拟机,它指的是 Java 虚拟机在执行过程中,碰到无法继续执行的异常状态,自动抛出异常。举例来说,Java 虚拟机在执行读取数组操作时,发现输入的索引值是负数,故而抛出数组索引越界异常(ArrayIndexOutOfBoundsException)。
捕获异常则涉及了如下三种代码块。
try 代码块:用来标记需要进行异常监控的代码。
catch 代码块:跟在 try 代码块之后,用来捕获在 try 代码块中触发的某种指定类型的异常。除了声明所捕获异常的类型之外,catch 代码块还定义了针对该异常类型的异常处理器。在 Java 中,try 代码块后面可以跟着多个 catch 代码块,来捕获不同类型的异常。Java 虚拟机会从上至下匹配异常处理器。因此,前面的 catch 代码块所捕获的异常类型不能覆盖后边的,否则编译器会报错。
finally 代码块:跟在 try 代码块和 catch 代码块之后,用来声明一段必定运行的代码。它的设计初衷是为了避免跳过某些关键的清理代码,例如关闭已打开的系统资源。
如果该异常被 catch 代码块捕获,finally 代码块则在 catch 代码块之后运行。在某些不幸的情况下,catch 代码块也触发了异常,那么 finally 代码块同样会运行,并会抛出 catch 代码块触发的异常。在某些极端不幸的情况下,finally 代码块也触发了异常,那么只好中断当前 finally 代码块的执行,并往外抛异常。
异常的基本概念
在 Java 语言规范中,所有异常都是 Throwable 类或者其子类的实例。Throwable 有两大直接子类。第一个是 Error,涵盖程序不应捕获的异常。当程序触发 Error 时,它的执行状态已经无法恢复,需要中止线程甚至是中止虚拟机。第二子类则是 Exception,涵盖程序可能需要捕获并且处理的异常。
Exception 有一个特殊的子类 RuntimeException,用来表示“程序虽然无法继续执行,但是还能抢救一下”的情况。前边提到的数组索引越界便是其中的一种。
RuntimeException 和 Error 属于 Java 里的非检查异常(unchecked exception)。其他异常则属于检查异常(checked exception)。在 Java 语法中,所有的检查异常都需要程序显式地捕获,或者在方法声明中用 throws 关键字标注。通常情况下,程序中自定义的异常应为检查异常,以便最大化利用 Java 编译器的编译时检查。
异常实例的构造十分昂贵。这是由于在构造异常实例时,Java 虚拟机便需要生成该异常的栈轨迹(stack trace)。该操作会逐一访问当前线程的 Java 栈帧,并且记录下各种调试信息,包括栈帧所指向方法的名字,方法所在的类名、文件名,以及在代码中的第几行触发该异常。
当然,在生成栈轨迹时,Java 虚拟机会忽略掉异常构造器以及填充栈帧的 Java 方法(Throwable.fillInStackTrace),直接从新建异常位置开始算起。此外,Java 虚拟机还会忽略标记为不可见的 Java 方法栈帧。我们在介绍 Lambda 的时候会看到具体的例子。
Java 虚拟机是如何捕获异常的?
在编译生成的字节码中,每个方法都附带一个异常表。异常表中的每一个条目代表一个异常处理器,并且由 from 指针、to 指针、target 指针以及所捕获的异常类型构成。这些指针的值是字节码索引(bytecode index,bci),用以定位字节码。
其中,from 指针和 to 指针标示了该异常处理器所监控的范围,例如 try 代码块所覆盖的范围。target 指针则指向异常处理器的起始位置,例如 catch 代码块的起始位置。
public static void main(String[] args) {
try {
mayThrowException();
} catch (Exception e) {
e.printStackTrace();
}
}
编译过后,该方法的异常表拥有一个条目。
当程序触发异常时,Java 虚拟机会从上至下遍历异常表中的所有条目。当触发异常的字节码的索引值在某个异常表条目的监控范围内,Java 虚拟机会判断所抛出的异常和该条目想要捕获的异常是否匹配。如果匹配,Java 虚拟机会将控制流转移至该条目 target 指针指向的字节码。
如果遍历完所有异常表条目,Java 虚拟机仍未匹配到异常处理器,那么它会弹出当前方法对应的 Java 栈帧,并且在调用者(caller)中重复上述操作。在最坏情况下,Java 虚拟机需要遍历当前线程 Java 栈上所有方法的异常表。
finally 代码块的编译比较复杂。当前版本 Java 编译器的做法,是复制 finally 代码块的内容,分别放在 try-catch 代码块所有正常执行路径以及异常执行路径的出口中。
针对异常执行路径,Java 编译器会生成一个或多个异常表条目,监控整个 try-catch 代码块,并且捕获所有种类的异常(在 javap 中以 any 指代)。这些异常表条目的 target 指针将指向另一份复制的 finally 代码块。并且,在这个 finally 代码块的最后,Java 编译器会重新抛出所捕获的异常。
这里有一个小问题,如果 catch 代码块捕获了异常,并且触发了另一个异常,那么 finally 捕获并且重抛的异常是哪个呢?答案是后者。也就是说原本的异常便会被忽略掉,这对于代码调试来说十分不利。
总结与实践
今天我介绍了 Java 虚拟机的异常处理机制。
Java 的异常分为 Exception 和 Error 两种,而 Exception 又分为 RuntimeException 和其他类型。RuntimeException 和 Error 属于非检查异常。其他的 Exception 皆属于检查异常,在触发时需要显式捕获,或者在方法头用 throws 关键字声明。
Java 字节码中,每个方法对应一个异常表。当程序触发异常时,Java 虚拟机将查找异常表,并依此决定需要将控制流转移至哪个异常处理器之中。Java 代码中的 catch 代码块和 finally 代码块都会生成异常表条目。
JVM总结-异常处理的更多相关文章
- JVM的异常处理
异常处理的两大组成要素:抛出异常和捕获异常.这两大要素共同实现程序控制流的非正常转移. 抛出异常分为:显式和隐式两种. 显式抛异常的主题是应用程序,它指的是在程序中使用 “throw” 关键字.手动 ...
- JVM 学习(一)反射、垃圾回收、异常处理--- 2019年4月
1.JVM 基础知识点 JVM 虚拟机包含了:自动内存管理器.垃圾回收(垃圾回收调优). 执行顺序:Java 代码 --- .class 字节码文件(加载到虚拟机中) --- Java 类放在方法区中 ...
- 四:JVM调优原理与常见异常处理方案
在jvm调优之前,我们必须先了解jvm的内存模型与GC回收机制,这些在我前面的文章里面有介绍!接下来我们通过一个案例来调整jvm性能. 一测试案例: 1.1 编写demo import java.te ...
- 基本数据类型 异常 数组排序 JVM区域划分
Day01 1.基本数据类型各占几个字节 Byte 1 short2 int4 long8 float4 double6 char2 boolean1 Byte b1=3,b2= ...
- java.lang.Throwable 异常/深入
有几个现象是需要总结的: -------------------------------------- 在java语言中,错误类的基类是java.lang.Error,异常类的基类是java.lang ...
- 黑马程序员_Java面向对象_异常
6.异常 1.异常: 就是程序在运行时出现不正常的情况.问题也是现实生活中一个具体的事物,也可以通过Java的类进行描述,并封装成对象.Exception和Error的子类名都是以父类名作为后缀名. ...
- Java并发学习 & Executor学习 & 异常逃逸 & 同步互斥Best Practice & wait/notify, conditon#await/signal
看了这篇文章:http://www.ciaoshen.com/2016/10/28/tij4-21/ 有一些Java并发的内容,另外查了一些资料. 朴素的Thread 首先,Java中关于线程Thre ...
- Java异常模块
JAVA异常的捕获与处理 视频链接:https://edu.aliyun.com/lesson_1011_8939#_8939 java语言提供最为强大的支持就在于异常的处理操作上. 1,认识异常对程 ...
- 流言粉碎机:JAVA使用 try catch 会严重影响性能
目录 一.JVM 异常处理逻辑 二.关于JVM的编译优化 1. 分层编译 2. 即时编译器 1. 解释模式 2. 编译模式 3. 提前编译器:jaotc 三.关于测试的约束 执行用时统计 编译器优化的 ...
随机推荐
- 基于tensorflow的MNIST手写识别
这个例子,是学习tensorflow的人员通常会用到的,也是基本的学习曲线中的一环.我也是! 这个例子很简单,这里,就是简单的说下,不同的tensorflow版本,相关的接口函数,可能会有不一样哟.在 ...
- python条件判断,真假
整形 非0即真 字符串 非空即真 列表,元组,字典,集合为空时 为假 函数 返回值为空(即函数中只写了return后面没参数),为0(即return 0),为空的列表,字典,集合.那么条件判断函数也是 ...
- C# 基于大整数类的RSA算法实现(公钥加密私钥解密,私钥加密公钥解密)
但是C#自带的RSA算法类RSACryptoServiceProvider只支持公钥加密私钥解密,即数字证书的使用. 所以参考了一些网上的资料写了一个RSA的算法实现.算法实现是基于网上提供的一个大整 ...
- Hadoop是怎么分块Block的?
不多说,直接上干货! hadoop的分块有两部分. 第一部分就是数据的划分(即把File划分成Block),这个是物理上真真实实的进行了划分,数据文件上传到HDFS里的时候,需要划分成一块一块,每块的 ...
- vc++获取网页源码之使用import+接口方式
1.使用IWinHttpRequest获取网页源码 首先要创建基于对话框的mfc应用程序 2.import+接口方式 首先导入winhttp.dll,使用IWinHttpRequest接口 #impo ...
- csrf攻击 使用js 调用 php文件的方法(还没实践)
https://www.cnblogs.com/Im-Victor/p/9306535.html
- Java-Runoob-高级教程-实例-方法:10. Java 实例 – 标签(Label)
ylbtech-Java-Runoob-高级教程-实例-方法:10. Java 实例 – 标签(Label) 1.返回顶部 1. Java 实例 - 标签(Label) Java 实例 Java 中 ...
- 1120 Friend Numbers (20 分)
1120 Friend Numbers (20 分) Two integers are called "friend numbers" if they share the same ...
- GO中DEFER的理解--DEFER执行的原理
在golang当中,defer代码块会在函数调用链表中增加一个函数调用.这个函数调用不是普通的函数调用,而是会在函数正常返回,也就是return之后添加一个函数调用.因此,defer通常用来释放函数内 ...
- go中的make和new的区别
适用范围:make 只能创建内建类型(slice map channel), new 则是可以对所有类型进行内存分配 返回值: new 返回指针, make 返回引用 填充值: new 填充零值, m ...