阿里P7浅析Java虚拟机如何处理异常

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创建并抛出。执行抛出的字节码如下表所示:

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://blog.51cto.com/13754022/2360205

时间: 2024-09-27 22:13:27

阿里P7浅析Java虚拟机如何处理异常的相关文章

Java虚拟机OutOfMemoryError 异常详解及解决方法(3)

上图是一张Java运行时的内存分布图,可知虚拟机内存都有发生OutOfMemoryError(下文称 OOM)异常的可能,作为一个合格的Java开发人员,我们应该做到的是: (1)第一,通过代码验证 Java 虚拟机规范中描述的各个运行时区域储存的内容: (2)第二,遇到内存溢出的时候,应该可以找打具体的位置,并进行合理的解决: 下边就聊一下 OOM: 一.Java 堆溢出 我们知道Java 堆用于储存对象实例,我们只要不断地创建对象,并且保证 GC Roots 到对象之间有可达路径来避免垃圾回

深入理解JAVA虚拟机之异常诊断

常见的JAVA虚拟机HotSpot虚拟机运行时数据库由5部分构成:方法区,堆,虚拟机栈,本地方法栈,程序计数器.下面列举各个部分可能出现的异常及其出现原因. 1.方法区存放的已被虚拟机加载的类型信息,常量.静态变量.即时编译器编译后的代码缓存等数据.可能出现的异常有OutOfMemoryError,原因可能是创建了过多的常量(不太可能,因为自JDK7起,原本存放在永久代中的字符串常量池被移至Java堆中,故JDK7前的运行池常量溢出报错由OOM:PerGem space变为了OOM:Java h

浅析Java虚拟机结构与机制

本文旨在给所有希望了解JVM(Java Virtual Machine)的同学一个概念性的入门,主要介绍了JVM的组成部分以及它们内部工作的机制和原理.当然本文只是一个简单的入门,不会涉及过多繁杂的参数和配置,感兴趣的同学可以做更深入的研究,在研究JVM的过程中会发现,其实JVM本身就是一个计算机体系结构,很多原理和我们平时的硬件.微机原理.操作系统都有十分相似的地方,所以学习JVM本身也是加深自我对计算机结构认识的一个很好的途径. 另外需要注意的是,虽然平时我们用的大多是Sun(现已被Orac

[转载]浅析Java虚拟机结构与机制

http://blog.hesey.net/2011/04/introduction-to-java-virtual-machine.html 本文旨在给所有希望了解JVM(Java Virtual Machine)的同学一个概念性的入门,主要介绍了JVM的组成部分以及它们内部工作的机制和原理.当然本文只是一个简单的入门,不会涉及过多繁杂的参数和配置,感兴趣的同学可以做更深入的研究,在研究JVM的过程中会发现,其实JVM本身就是一个计算机体系结构,很多原理和我们平时的硬件.微机原理.操作系统都有

浅析Java虚拟机结构与机制[转]

本文旨在给所有希望了解JVM(Java Virtual Machine)的同学一个概念性的入门,主要介绍了JVM的组成部分以及它们内部工作的机制和原理.当然本文只是一个简单的入门,不会涉及过多繁杂的参数和配置,感兴趣的同学可以做更深入的研究,在研究JVM的过程中会发现,其实JVM本身就是一个计算机体系结构,很多原理和我们平时的硬件.微机原理.操作系统都有十分相似的地方,所以学习JVM本身也是加深自我对计算机结构认识的一个很好的途径. 另外需要注意的是,虽然平时我们用的大多是Sun(现已被Orac

java虚拟机规范阅读(三)异常

Java虚拟机里面的异常使用Throwable或其子类的实例来表示,抛异常的本质实际上是程序控制权的一种即时的.非局部(Nonlocal)的转换--从异常抛出的地方转换至处理异常的地方. 绝大多数的异常的产生都是由于当前线程执行的某个操作所导致的,这种可以称为是同步的异常.与之相对的,异步异常是指在程序的其他任意地方进行的动作而导致的异常.Java虚拟机中异常的出现总是由下面三种原因之一导致的: 1.虚拟机同步检测到程序发生了非正常的执行情况,这时异常将会紧接着在发生非正常执行情况的字节码指令之

Java虚拟机结构分析

本博文主要介绍了JVM(Java Virtual Machine)的组成部分以及它们内部的工作机制和原理.需要注意的是,虽然平时我们用的大多是Sun(现已被Oracle收购)JDK提供的JVM,但是JVM本身是一个规范,所以可以有多种实现,除了Hotspot外,还有诸如Oracle的JRockit.IBM的J9也都是非常有名的JVM. 1. 结构 下图展示了JVM的主要结构: 可以看出,JVM主要由类加载器子系统.运行时数据区(内存空间).执行引擎以及与本地方法接口等组成.其中运行时数据区又由方

阿里P7架构师对Java虚拟机、类加载机制是怎么理解的?

概述 类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载 (Loading).验证(Verification).准备(Preparation).解析(Resolution).初始化 (Initialization).使用(Using)和卸载(Unloading)7 个阶段.其中验证.准备.解析 3 个部分统称为连接(Linking) 于初始化阶段,虚拟机规范则是严格规定了有且只有 5 种情况必须立即对类进行“初始 化”(而加载.验证.准备自然需要在此之前开始): 1)遇到

了解OutOfMemoryError异常 - 深入Java虚拟机读后总结

JVM中的异常发生 Java虚拟机规范中除了程序计数器外,其他几个运行时区域都有发生OutOfMemoryError异常的可能. 本章笔记通过代码来验证Java虚拟机规范中描述的各个运行时区域存储的内容.以及在以后遇到实际的内存溢出异常时,能根据异常的信息快速判断是哪个区域出现的内存溢出.怎样的代码可能会导致这些区域的内存溢出.以及这些问题该如何处理. Java堆溢出:Java堆用于存储对象实例,只要不断的创建对象,并且保证GC Roots到对象之间有可达路径避免垃圾回收机制清除对象,就会在对象