Java虚拟机如何处理异常

144882c0-8f13-4908-811a-c87b10bdff14.gif

本专栏的目的是使Java开发人员了解运行中的Java程序下单击和旋转的神秘机制。本月的文章通过检查Java虚拟机处理异常引发和捕获的方式(包括相关的字节码),继续讨论Java虚拟机的字节码指令集。本文不讨论finally条款-这是下个月的主题。随后的文章将讨论字节码家族的其他成员。


例外情况

异常使您可以顺利处理程序运行时发生的意外情况。为了演示Java虚拟机处理异常的方式,请考虑一个名为

NitPickyMath

提供了对整数执行加,减,乘,除和除法的方法。

NitPickyMath

执行这些数学运算的方式与Java的“ +”,“-”,“ *”,“ /”和“%”运算符提供的常规运算相同,除了

NitPickyMath

在溢出,下溢和被零除的情况下引发检查的异常。Java虚拟机将抛出一个

ArithmeticException

整数除以零,但不会在溢出和下溢时引发任何异常。方法抛出的异常

NitPickyMath

定义如下:

class OverflowException extends Exception {

class UnderflowException extends Exception {} 

class DivideByZeroException extends Exception {}

捕获并引发异常的简单方法是remainderclass方法NitPickyMath

static int remainder(int dividend, int divisor)
throws DivideByZeroException {
try {
return dividend % divisor;
}
catch (ArithmeticException e) {
throw new DivideByZeroException();
}}

remainder方法仅对作为参数传递的两个int执行其余操作如果余数运算ArithmeticException的除数为零,则余数运算将引发此方法捕获此错误ArithmeticException并引发DivideByZeroException

DivideByZeroArithmeticException例外之间的区别在于DivideByZeroException是已检查的异常而未检查ArithmeticException由于未经检查,因此即使方法可能抛出异常,也无需在throws子句中声明此异常。作为或的子类的任何异常都未经检查。是的子类。)通过捕获然后抛出,该方法强制其客户端通过捕获它或在自己的throws子句中声明来解决被零除的异常的可能性这是因为检查异常,例如ArithmeticExceptionErrorRuntimeExceptionArithmeticExceptionRuntimeExceptionArithmeticExceptionDivideByZeroExceptionremainderDivideByZeroExceptionDivideByZeroException,在方法内抛出的异常必须被该方法捕获或在该方法的throws子句中声明。未抛出异常(例如ArithmeticException)无需在throws子句中捕获或声明。

javac为该remainder方法生成以下字节码序列

The main bytecode sequence for remainder:
0 iload_0 // Push local variable 0 (arg passed as divisor)
1 iload_1 // Push local variable 1 (arg passed as dividend)
2 irem // Pop divisor, pop dividend, push remainder
3 ireturn // Return int on top of stack (the remainder)The bytecode sequence for the catch (ArithmeticException) clause:
4 pop // Pop the reference to the ArithmeticException
// because it isn't used by this catch clause.
5 new #5 <Class DivideByZeroException>
// Create and push reference to new object of class
// DivideByZeroException.DivideByZeroException
8 dup // Duplicate the reference to the new
// object on the top of the stack because it
// must be both initialized
// and thrown. The initialization will consume
// the copy of the reference created by the dup.
9 invokenonvirtual #9 <Method DivideByZeroException.<init>()V>
// Call the constructor for the DivideByZeroException
// to initialize it. This instruction
// will pop the top reference to the object.
12 athrow // Pop the reference to a Throwable object, in this
// case the DivideByZeroException,
// and throw the exception.

remainder方法的字节码序列有两个单独的部分。第一部分是该方法的正常执行路径。这部分从pc偏移量0到3。第二部分是catch子句,它从pc偏移量4到12。

主字节码序列中irem指令可能抛出ArithmeticException如果发生这种情况,Java虚拟机将通过查找并在表中查找异常来跳转到实现catch子句的字节码序列。每个捕获异常的方法都与一个异常表相关联,该异常表与该方法的字节码序列一起在类文件中提供。对于每个try块捕获的每个异常,异常表都有一个条目。每个条目都有四段信息:起点和终点,要跳转到的字节码序列内的pc偏移以及要捕获的异常类的常量池索引。remainder类方法的异常表NitPickyMath如下所示:

Exception table:
from to target type
0 4 4 <Class java.lang.ArithmeticException>

上面的异常表表明ArithmeticException捕获了从pc偏移量0到3(含3)的数据表中标签为“ to”的try块的端点值始终比捕获异常的最后一个pc偏移量大一个。在这种情况下,端点值列出为4,但是捕获到异常的最后一个pc偏移为3。此范围(从零到三)(包括零),对应于在的try块内实现代码的字节码序列remainder表格中列出的目标是pc偏移量(如果ArithmeticException在pc偏移量0与3之间(含零)之间抛出an,跳至该偏移量)。

如果在方法执行期间引发异常,则Java虚拟机将在异常表中搜索匹配的条目。如果当前程序计数器在条目指定的范围内,并且抛出的异常类是条目指定的异常类(或者是指定异常类的子类),则异常表条目匹配。Java虚拟机按照条目在表中出现的顺序搜索异常表。找到第一个匹配项后,Java虚拟机将程序计数器设置为新的pc偏移位置,并在该位置继续执行。如果未找到匹配项,则Java虚拟机将弹出当前堆栈帧并抛出相同的异常。当Java虚拟机弹出当前堆栈帧时,它有效地中止了当前方法的执行,并返回到调用此方法的方法。但是,它没有在先前的方法中正常继续执行,而是在该方法中引发了相同的异常,这使Java虚拟机经历了相同的搜索该方法的异常表的过程。

Java程序员可以使用throw语句引发异常,例如a的catch(ArithmeticException)子句中的语句remainder,在该语句中DivideByZeroException创建并引发a。下表显示了进行抛出的字节码:

抛出异常
操作码操作数描述
投掷(没有)弹出Throwable对象引用,引发异常

所述athrow指令从堆栈中弹出顶部字和希望它是一个对象,它是一个子类的引用Throwable(或Throwable本身)。抛出的异常是由弹出的对象引用定义的类型。

Play Ball!:Java虚拟机模拟

下面的小程序演示了执行字节码序列的Java虚拟机。模拟中的字节码序列由

Java语言

为了

playBall

该类的方法如下所示:

class Ball extends Exception {}

class Pitcher {

private static Ball ball = new Ball();
static void playBall() {
int i = 0;
while (true) {
try {
if (i % 4 == 3) {
throw ball;
}
++i;
}
catch (Ball b) {
i = 0;
}
}
}}

javac为该playBall方法生成的字节码如下所示:


0 iconst_0 // Push constant 0
1 istore_0 // Pop into local var 0: int i = 0;
// The try block starts here (see exception table, below).
2 iload_0 // Push local var 0
3 iconst_4 // Push constant 4
4 irem // Calc remainder of top two operands
5 iconst_3 // Push constant 3
6 if_icmpne 13 // Jump if remainder not equal to 3: if (i % 4 == 3) {
// Push the static field at constant pool location #5,
// which is the Ball exception itching to be thrown
9 getstatic #5 <Field Pitcher.ball LBall;>
12 athrow // Heave it home: throw ball;
13 iinc 0 1 // Increment the int at local var 0 by 1: ++i;
// The try block ends here (see exception table, below).
16 goto 2 // jump always back to 2: while (true) {}
// The following bytecodes implement the catch clause:
19 pop // Pop the exception reference because it is unused
20 iconst_0 // Push constant 0
21 istore_0 // Pop into local var 0: i = 0;
22 goto 2 // Jump always back to 2: while (true) {}Exception table:
from to target type
2 16 19 <Class Ball>

playball方法将永远循环。每四分之一的循环中,玩球都会抛出a Ball并接住它,只是因为这很有趣。因为try块和catch子句都在无尽的while循环中,所以乐趣永不停止。局部变量i从0开始,并在每次循环中递增。if语句true为时,每当等于3 时都会发生Ball则抛出异常。

Java虚拟机检查异常表并发现确实存在适用的条目。条目的有效范围是2到15,包括端值,并且在pc偏移量12处引发了异常。条目捕获的异常是class Ball,并且引发的异常是class Ball有了这种完美匹配,Java虚拟机将抛出的异常对象压入堆栈,并在pc偏移量19处继续执行。catch子句仅将int i重置为0,然后循环重新开始。

要驱动仿真,只需按下“ Step”按钮。每按一次“ Step”按钮,将使Java虚拟机执行一个字节码指令。要重新开始仿真,请按下“复位”按钮。要使Java虚拟机重复执行字节码而无需您再进行任何同轴电缆连接,请按“运行”按钮。然后,Java虚拟机将执行字节码,直到按下“停止”按钮为止。小程序底部的文本区域描述了要执行的下一条指令。点击愉快。

Bill Venners从事专业软件编写已有12年了。他位于硅谷,以Artima Software Company的名义提供软件咨询和培训服务。多年来,他为消费电子,教育,半导体和人寿保险行业开发了软件。他在许多平台上用多种语言编程:各种微处理器上的汇编语言,Unix上的C,Windows上的C ++,Web上的Java。他是McGraw-Hill出版的《 Java虚拟机内部》一书的作者。



长按识别

dbdfec99-47a2-46ae-a891-3824f02fb93d.jpg



19176f47-52c8-4f93-9b79-2f3c8f3b71a2.png 你点的每个在看,我都认真当成了喜欢

本文分享自微信公众号 - 黑帽子技术(SNJYYNJY2020)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值