评论

收藏

[Java] Java虚拟机处理异常的最佳方式

编程语言 编程语言 发布于:2021-09-18 14:36 | 阅读数:303 | 评论:0

这篇文章主要给大家介绍了关于Java虚拟机处理异常的最佳方式,文中通过示例代码介绍的非常详细,对大家的学习或者使用Java具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
前言
欢迎来到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。
dividebyzeroexception和arithmeticexception之间的差别是dividebyzeroexception是一个检查异常,并且arithmeticexception是未经检查。因为arithmeticexception是非受检异常,所以方法不需要在throws子句中声明此异常,即使它可能会抛出它。任何属于error或者runtimeexception子类的异常都是非受检异常。(arithmeticexception是runtimeexception的子类。)通过捕获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创建并抛出。执行抛出的字节码如下表所示:
opcodeoperand(s)descriptionathrow(none)pops throwable object reference, throws the exceptionathrow指令从堆栈中弹出顶部字节,并且会认为它是一个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
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对CodeAE代码之家的支持。
原文链接:http://www.apexyun.com/javaxu-ni-ji-ru-he-chu-li-yi-chang

关注下面的标签,发现更多相似文章