欢迎来到“Under The Hood”第六期。本期我们介绍JVM处理异常的方式,包括如何抛出和捕获异常及相关的字节码指令。但本文不会讨论finally子句,这是下期的主题。你可能需要阅读往期的文章才能更好的理解本文。
异常处理
在程序运行时,异常让你可以平滑的处理意外状况。为了演示JVM处理异常的方式,考虑NitPickyMath类,它提供对整数进行加,减,乘,除以及取余的操作。
NitPickyMath提供的这些操作和Java语言的“+”,“-”,“*”,“/”和“%”是一样的,除了NitPickyMath中的方法在以下情况下会抛出检查型(checked)异常:上溢出,下溢出以及被0除。0做除数时,JVM会抛出ArithmeticException异常,但是上溢出和下溢出不会引发任何异常。NitPickyMath中抛出异常的方法定义如下:
class OverflowException extends Exception { } class UnderflowException extends Exception { } class DivideByZeroException extends Exception { }
NitPickyMath类中的remainder()方法就是一个抛出和捕获异常的简单方法。
static int remainder(int dividend, int divisor) throws DivideByZeroException { try { return dividend % divisor; } catch (ArithmeticException e) { throw new DivideByZeroException(); } }
remainder()方法,只是简单的对当作参数传递进来的2个整数进行取余操作。如果取余操作的除数是0,会引发ArithmeticException异常。remainder()方法捕获这个异常,并重新抛出DivideByZeroException异常。
DivideByZeroException和ArithmeticException的区别是,DivideByZeroException是检查型(checked)异常,而ArithmeticException是非检查(unchecked)型异常。由于ArithmeticException是非检查型异常,一个方法就算会抛出该异常,也不必在其throw子句中声明它。任何Error或RuntimeException异常的子类异常都是非检查型异常。(ArithmeticException就是RuntimeException的子类。)通过捕获ArithmeticException和抛出DivideByZeroException,remainder()方法强迫它的调用者去处理除数为0的可能性,要么捕获它,要么在其throw子句中声明DivideByZeroException异常。这是因为,像DivideByZeroException这种在方法中抛出的检查型异常,要么在方法中捕获,要么在其throw子句中声明,二者必选其一。而像ArithmeticException这种非检查型异常,就不需要去显式捕获和声明。
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 is not used by this catch clause. 5 new #5 < Class DivideByZeroException > // Create and push reference to new object of class // 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()方法的字节码有2个单独的部分。第一部分是该方法的正常执行路径,这部分从第0行开始,到第3行结束。第二部分是从第4行开始,到12行结束的catch子句。
主字节码序列中的irem指令可能会抛出ArithmeticException异常。如果异常发生了,JVM通过在异常表中查找匹配的异常,它会知道要跳转到相应的异常处理的catch子句的字节码序列部分。每个捕获异常的方法,都跟类文件中与方法字节码一起交付的异常表关联。每一个捕获异常的try块,都是异常表中的一行。每行4条信息:开始行号(from)和结束行号(to),要跳转的字节码序列行号(target),被捕获的异常类的常量池索引(type)。remainder()方法的异常表如下所示:
from | to | target | type |
0 | 4 | 4 | < Class java.lang.ArithmeticException > |
上面的异常表表明,行号1到3范围内,ArithmeticException将被捕获。异常表中的“to”下面的结束行号始终比异常捕获的最大行号大1,上表中,结束行号为4,而异常捕获的最大行号是3。行号0到3的字节码序列对应remainder()方法中的try块。“target”列中,是行0到3的字节码发生ArithmeticException异常时要跳转到的目标行号。
如果方法执行过程中产生了异常,JVM会在异常表中查找匹配行。异常表中的匹配行要符合下面的条件:当前pc寄存器的值要在该行的表示范围之内,[from, to),且产生的异常是该行所指定的异常类或其子类。JVM按从上到下的次序查找异常表。当找到了第一个匹配行,JVM把pc寄存器设为新的跳转行号,从此行继续往下执行。如果找不到匹配行,JVM弹出当前栈帧,并重新抛出同一个异常。当JVM弹出当前栈帧时,它会终止当前方法的执行,返回到调用该方法的上一个方法那里。这时,在上一个方法里,并不会继续正常的执行过程,而是抛出同样的异常,促使JVM重新查找该方法的异常表。
Java程序员可以用throw语句抛出像remainder()方法的catch子句中的异常,DivideByZeroException。下表列出了抛出异常的字节码:
OPCODE | OPERAND(S) | DESCRIPTION |
athrow | (none) | pops Throwable object reference, throws the exception |
athrow指令把栈顶元素弹出,该元素必须是Throwable的子类或其自身的对象引用,而抛出的异常类型由栈顶弹出的对象引用所指明。
相关推荐
异常处理的两大元素:抛出异常、捕获异常,非正常处理的两个方法。下面这篇文章主要给大家介绍了关于JVM如何处理异常的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
Java异常处理 声明异常 抛出异常 捕获异常 如何选择异常类型 常见异常处理方式 直接抛出异常 封装异常再抛出 捕获异常 自定义异常 try-catch-finally try-with-resource Java异常常见面试题 1. Error 和 Exception ...
1. try 代码块:用来标记需要进行异常监控的代码 2. catch 代码块:跟在 try 代码块之后,用来捕获在 try 代码块中触发的某种指定类型的异常
•为了保证一定能回收try块中打开的物理资源,异常处理机制提供了finally块。不管try块中的代码是 否出现异常,也不管哪一个catch块被执行,finally块总会被执行。 异常处理的嵌套 •异常处理流程代码...
异常处理机制通常由编译器和异常处理机制的运行时支持函数共同实现,因此,如何正确高效地实现异常处理机制是设计编译器和异常处理运行时支持函数所要关心的重要问题。 Java程序的编译运行有两种方式:在JVM上动态编译...
Java集合、JVM面试题,包括Java集合、JVM内存模型、垃圾回收机制、JVM调优、异常处理等相关面试题 适用于Java编程的初学者,加强自身对于Java集合、JVM、异常处理等方面的知识储备,更好的应对面试机会
异常处理:JVM 提供了强大的异常处理机制。Java程序可以通过抛出异常和捕获异常来处理各种错误和异常情况。这提供了一种结构化的方式来处理错误,增加了程序的可靠性和可维护性。 多线程支持:JVM 提供了对多线程...
内容概要:以上列出的Java面试题涵盖了Java语言的基础知识、面向对象编程、集合、IO流、多线程、反射、类加载器、JVM、序列化、泛型、异常处理、注解等多个方面。 适用人群:以上Java面试题适用于准备Java开发...
掌握条件分支、循环控制、异常处理、构造方法在字节码级别的实现原理,利用HSDB工具理解多态原理。还会涉及从编译期的语法糖处理,到类加载的各个阶段,直至运行期的各项优化的详细讲解。最后不要错过方法反射优化的...
Java程序运行过程中所发生的异常事件从严重性可分为两类: 错误(Error):JVM系统内部错误或资源耗尽等严重情况-属于JVM需要负担的责任 这一类异常事件无法恢复或不可能捕获,将导致应用程序中断。 异常(Exception)...
主要给大家介绍了关于JVM处理未捕获异常的相关资料,文中通过示例代码以及图文介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
解决问题关于tomcat的端口异常错误信息
工具是运用知识处理数据的手段。这里说的数据包括:运行日志、异常堆栈、GC日志、线程快照文件(threaddump/javacore文件)、堆转储快照(heapdump/hprof文件)等。使用JVM命令和一查看这个JVM参数,帮助我们排查、...
如果java提供的系统异常类型不能满足程序设计的需求,那么可以设计自己的异常类型。 从java异常类的结构层次...另外一种问题是程序运行错误,java定义为Exception,这种情况下,可以通过程序设计调整来实现异常处理。
第84讲 异常处理指令 00:09:44 第85讲 同步指令 00:07:34 第86讲 类加载机制概述 00:07:26 第87讲 类加载时机 00:13:15 第88讲 类加载的过程-加载 00:15:15 第89讲 类加载的过程-验证 00:10:24 ...
·课程中,Eclipse和IDEA这两种企业一线开发环境都使用到了 3.技术讲解更深入、更全面: ·课程共30天,715个知识视频小节,涉及主流Java使用的方方面面,全而不冗余 ·全程内容涵盖数据结构、设计模式、JVM内存...
第84节异常处理指令00:09:44分钟 | 第85节同步指令00:07:34分钟 | 第86节类加载机制概述00:07:26分钟 | 第87节类加载时机00:13:15分钟 | 第88节类加载的过程-加载00:15:15分钟 | 第89节类加载的过程-验证00:10:...
Java面试准备——异常处理 本文学习自GitHub上的JavaGuide项目,感谢大佬的资源,此处为自我学习与整理,原项目链接 JavaGuide Java异常类层次结构图 Java中所有的异常都有一个祖先java.lang.Throwable。Throwable...
Java 异常处理 没有程序能够始终正常运行,Java 语言的设计者也知道这一点。Java 平台提供了内置机制来处理代码未准确地按计划运行的情形。 异常 是在程序执行期间发生的破坏正常的程序指令流的事件。 异常处理 可以...
/ 112 5.2.5 服务器JVM进程崩溃 / 113 5.3 实战:Eclipse运行速度调优 / 114 5.3.1 调优前的程序运行状态 / 114 5.3.2 升级JDK 1.6的性能变化及兼容问题 / 117 5.3.3 编译时间和类加载时间的优化 / 122 5.3.4 ...