Java虚拟机如何处理异常

栏目: Java · 发布时间: 5年前

内容简介:欢迎来到Exceptions允许您顺利处理程序运行时发生的意外情况。要演示Java虚拟机处理异常的方式,请考虑一个名为NitPickyMath的类。它提供了对整数执行加法,减法,乘法,除法和余数的方法。NitPickyMath在溢出,下溢和被零除的条件下抛出已检查的异常。Java虚拟机将在整数除零上抛出一个ArithmeticException,但不会在溢出和下溢上抛出任何异常。方法抛出的异常定义如下:捕获和抛出异常的简单方法是

欢迎来到 Under The Hood专栏 。本专栏旨在让 Java 开发人员一瞥在运行Java程序底层的神秘机制。本月的文章继续讨论Java虚拟机的字节码指令集,方法是检查Java虚拟机处理异常抛出和捕获的方式,包括相关的字节码。本文不讨论 finally 条款 - 这是下个月的主题。后续文章将讨论字节码系列的其他成员。

Exceptions

Exceptions允许您顺利处理程序运行时发生的意外情况。要演示Java虚拟机处理异常的方式,请考虑一个名为NitPickyMath的类。它提供了对整数执行加法,减法,乘法,除法和余数的方法。NitPickyMath在溢出,下溢和被零除的条件下抛出已检查的异常。Java虚拟机将在整数除零上抛出一个ArithmeticException,但不会在溢出和下溢上抛出任何异常。方法抛出的异常定义如下:

class OverflowException extends Exception {
}
class UnderflowException extends Exception {
}
class DivideByZeroException extends Exception {
}

捕获和抛出异常的简单方法是 remainder 类的方法 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

DivideByZeroExceptionArithmeticException 之间的差别是 DivideByZeroException 是一个 检查 异常,并且 ArithmeticException未经检查 。因为 ArithmeticException 是非受检异常,所以方法不需要在throws子句中声明此异常,即使它可能会抛出它。任何属于 Error 或者 RuntimeException 子类的异常都是非受检异常。( ArithmeticExceptionRuntimeException 的子类。)通过捕获 ArithmeticException 然后抛出 DivideByZeroException ,该 remainder 方法强制其客户端处理除零异常的可能性,通过捕获它或在自己的throws子句中声明 DivideByZeroException 。这是因为已检查的异常,例如 DivideByZeroException ,抛出方法必须由方法捕获或在方法的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>

上面的异常表指示从pc偏移0到3(包括0),表示 ArithmeticException 将被捕获的范围。在标签“to”下面的表中列出的是try块的端点值,它总是比捕获异常的最后一个pc偏移量多一。在这种情况下,端点值列为4,捕获到异常的最后一个pc偏移量为3。此范围(包括0到3)对应于在 remainder 的try块内实现代码的字节码序列。如果 ArithmeticException 在pc偏移量为0和3之间(包括0和3)之间抛出,则表中列出的"to"就是跳转到的pc偏移量。

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

Java程序员可以使用throw语句抛出异常,例如 remainder 中的一个子句catch( ArithmeticException ),其中一个 DivideByZeroException 创建并抛出。执行抛出的字节码如下表所示:

Opcode Operand(s) Description
athrow (none) pops Throwable object reference, throws the exception

athrow 指令从堆栈中弹出顶部字节,并且会认为它是一个 Throwable 子类的引用(或 Throwable 本身)。抛出的异常是弹出对象引用定义的类型。

Play Ball!: a Java virtual machine simulation

下面的applet演示了一个执行一系列字节码的Java虚拟机。模拟中的字节码序列由javac生成。

类的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 方法永远循环。每四次循环,playball抛出 Ball 并抓住它,只是因为它很有趣。因为try块和catch子句都在无限循环中,所以乐趣永远不会停止。局部变量 i 从0开始,每次递增递增循环。当 if 语句出现 true 时,每次 i 等于3 时都会发生 Ball 异常,抛出异常。

Java虚拟机检查异常表并发现确实存在适用的条目。条目的有效范围是2到15(包括两者),异常在pc偏移12处抛出。条目捕获的异常是类 Ball ,抛出的异常是类 Ball 。鉴于这种完美匹配,Java虚拟机将抛出的异常对象推送到堆栈,并继续在pc偏移19处执行catch子句,这里仅将 int i 重置为0,并且循环重新开始。

要驱动模拟,只需按“步骤”按钮。每次按下“Step”按钮都会使Java虚拟机执行一个字节码指令。要开始模拟,请按“重置”按钮。要使Java虚拟机重复执行字节码而不需要进一步操作,请按“运行”按钮。然后,Java虚拟机将执行字节码,直到按下“停止”按钮。applet底部的文本区域描述了要执行的下一条指令。快乐点击。

英文原文: https://www.javaworld.com/article/2076868/how-the-java-virtual-machine-handles-exceptions.html


以上所述就是小编给大家介绍的《Java虚拟机如何处理异常》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

电商产品经理宝典:电商后台系统产品逻辑全解析

电商产品经理宝典:电商后台系统产品逻辑全解析

刘志远 / 电子工业出版社 / 2017-10-1 / 49.00元

时至今日,对于产品经理的要求趋向业务型、平台型,甚至产生了细分领域专家。纯粹的前端产品经理(页面、交互)逐渐失去竞争力。而当后台产品经理的视野开始从功能延伸到模块,再延伸到子系统,最后关注整体系统时,就有了把控平台型产品的能力。 《电商产品经理宝典:电商后台系统产品逻辑全解析》围绕“电商后台产品”,从电商的整体产品架构入手,逐步剖析各支撑子系统。通过学习电商产品后台的架构和逻辑,可以让读者从......一起来看看 《电商产品经理宝典:电商后台系统产品逻辑全解析》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

在线进制转换器
在线进制转换器

各进制数互转换器

URL 编码/解码
URL 编码/解码

URL 编码/解码