Exceptions容许您顺利处理程序运行时发生的意外状况。要演示Java虚拟机处理异常的方式,请考虑一个名为NitPickyMath的类。它提供了对整数执行加法,减法,乘法,除法和余数的方法。NitPickyMath在溢出,下溢和被零除的条件下抛出已检查的异常。Java虚拟机将在整数除零上抛出一个ArithmeticException,但不会在溢出和下溢上抛出任何异常。方法抛出的异常定义以下:java
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
。DivideByZeroException
和ArithmeticException
之间的差异是 DivideByZeroException
是一个 检查 异常,而且ArithmeticException
是 未经检查 。由于ArithmeticException
是非受检异常,因此方法不须要在throws子句中声明此异常,即便它可能会抛出它。任何属于 Error 或者 RuntimeException
子类的异常都是非受检异常。(ArithmeticException
是 RuntimeException
的子类。)经过捕获ArithmeticException
而后抛出 DivideByZeroException
,该 remainder
方法强制其客户端处理除零异常的可能性,经过捕获它或在本身的throws子句中声明 DivideByZeroException
。这是由于已检查的异常,例如 DivideByZeroException
,抛出方法必须由方法捕获或在方法的throws子句中声明。未经检查的异常(例如 ArithmeticException
,不须要在throws子句中捕获或声明)。sql
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 方法的异常表以下所示:app
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虚拟机经历搜索该方法的异常表的相同过程。ide
Java程序员能够使用throw语句抛出异常,例如 remainder
中的一个子句catch( ArithmeticException
),其中一个 DivideByZeroException
建立并抛出。执行抛出的字节码以下表所示:微服务
athrow 指令从堆栈中弹出顶部字节,而且会认为它是一个 Throwable
子类的引用(或 Throwable
自己)。抛出的异常是弹出对象引用定义的类型。高并发
下面的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底部的文本区域描述了要执行的下一条指令。快乐点击。
不单单是虚拟机,针对Java的Kafka、Mysql、Tomcat、Docker、Spring、MyBatis、Nginx、Netty、Dubbo、Redis、Netty、Spring cloud、分布式、高并发、性能调优、微服务等架构技术,我这边都给大伙儿找了点资料,但愿能帮助到有须要的人,记得私信我哟
怎么领取 → 进粉丝群:963944895,私聊管理员便可
既然看到这里了,以为笔者写的还不错的就点个赞,加个关注呗!点关注,不迷路,持续更新!!!