3.12 抛出和处理异常

  在程序中使用throw关键字来抛出异常。编译结果很简单。

void cantBeZero(int i) throws TestExc {
if (i == 0) {
throw new TestExc();
}
}

  编译为:

Method void cantBeZero(int)
iload_1 // Push argument (i)
ifne // If i==, allocate instance and throw
new # // Create instance of TestExc
dup // One reference goes to its constructor
invokespecial # // Method TestExc.<init>()V
athrow // Second reference is thrown
return // Never get here if we threw TestExc

  使用try-catch构建的编译时直观的,例如:

void catchOne() {
try {
tryItOut();
} catch (TestExc e) {
handleExc(e);
}
}

  编译为:

Method void catchOne()
aload_0 // Beginning of try block
invokevirtual # // Method Example.tryItOut()V
return // End of try block; normal return
astore_1 // Store thrown value in local var
aload_0 // Push this
aload_1 // Push thrown value
invokevirtual # // Invoke handler method:
// Example.handleExc(LTestExc;)V
return // Return after handling TestExc
Exception table:
From To Target Type
Class TestExc

  自己观察,try代码块编译后,就和try不存在一样:

Method void catchOne()
aload_0 // Beginning of try block
invokevirtual # // Method Example.tryItOut()V
return // End of try block; normal return

  在执行try代码块的时候,如果没有异常抛出,就和try不存在一样:tryItOut被调用然后catchOne返回。

  在try块后面是实现单个catch语句的Java虚拟机代码。

   astore_1            // Store thrown value in local var
aload_0 // Push this
aload_1 // Push thrown value
invokevirtual # // Invoke handler method:
// Example.handleExc(LTestExc;)V
return // Return after handling TestExc
Exception table:
From To Target Type
Class TestExc

  catch语句中的的内容——调用handlwExc,同样被编译为一个普通的方法调用。但是,catch语句的存在导致编译器生成一个异常表条目。catchOne方法的异常表有一个条目对应于catchOne的catch子句可以处理的一个参数(类TestExc的实例)。如果TestExc的某一个实例在指令执行catchOne的索引0到4之间抛出了,那么控制将转移到代码所有为5的位置,也就是实现了catch语句的代码块。如果抛出的异常不是TestExc的实例,catchOne的catch语句无法处理它。相应的,异常会抛给catchOne的调用者。

  try可以有多个catch语句:

void catchTwo() {
try {
tryItOut();
} catch (TestExc1 e) {
handleExc(e);
} catch (TestExc2 e) {
handleExc(e);
}
}

  通过简单地为每个catch子句依次附加Java虚拟机代码并将条目添加到异常表中来编译给定try语句的多个catch子句,如下所示:

Method void catchTwo()
aload_0 // Begin try block
invokevirtual # // Method Example.tryItOut()V
return // End of try block; normal return
astore_1 // Beginning of handler for TestExc1;
// Store thrown value in local var
aload_0 // Push this
aload_1 // Push thrown value
invokevirtual # // Invoke handler method:
// Example.handleExc(LTestExc1;)V
return // Return after handling TestExc1
astore_1 // Beginning of handler for TestExc2;
// Store thrown value in local var
aload_0 // Push this
aload_1 // Push thrown value
invokevirtual # // Invoke handler method:
// Example.handleExc(LTestExc2;)V
return // Return after handling TestExc2
Exception table:
From To Target Type
Class TestExc1
Class TestExc2

  如果在执行try子句期间(在索引0和4之间)抛出一个值,该值与一个或多个catch子句的参数匹配(值是一个或多个参数的实例),则选择第一个匹配的catch语句。 控制转移到该catch语句的块的Java虚拟机代码。 如果抛出的值与catchTwo的任何catch子句的参数不匹配,则Java虚拟机将重新抛出该值,而不调用catchTwo的任何catch语句中的代码。

  嵌套的try-catch语句编译结果和try语句跟随多个catch是非常相似的::

void nestedCatch() {
try {
try {
tryItOut();
} catch (TestExc1 e) {
handleExc1(e);
}
} catch (TestExc2 e) {
handleExc2(e);
}
}

  编译为:

Method void nestedCatch()
aload_0 // Begin try block
invokevirtual # // Method Example.tryItOut()V
return // End of try block; normal return
astore_1 // Beginning of handler for TestExc1;
// Store thrown value in local var
aload_0 // Push this
aload_1 // Push thrown value
invokevirtual # // Invoke handler method:
// Example.handleExc1(LTestExc1;)V
return // Return after handling TestExc1
astore_1 // Beginning of handler for TestExc2;
// Store thrown value in local var
aload_0 // Push this
aload_1 // Push thrown value
invokevirtual # // Invoke handler method:
// Example.handleExc2(LTestExc2;)V
return // Return after handling TestExc2
Exception table:
From To Target Type
Class TestExc1
Class TestExc2

  catch语句的嵌套仅表现在异常表中,java虚拟机本身不要求异常表条目的顺序。但是,因为try-catch构造是结构化的,所以编译器总是可以对异常处理程序表的条目进行排序,这样,对于任何抛出的异常,都会被最接近异常位置的、可处理该异常的catch语句块所处理。

  例如,如果tryItOut(在索引1处)的调用抛出TestExc1的实例,则它将由调用handleExc1的catch语句处理。 即使异常发生在外部catch语句能够处理的范围之内(捕获TestExc2的catch语句),并且外部的catch能够处理这个异常,也不会分配给外部的catch处理,因为异常表的顺序。

  作为一个微妙的点,请注意catch子句的范围包含在“from”端,不包括“to”端(第4.7.3节)。 因此,捕获TestExc1的catch子句的异常表条目不包括偏移量为4的return指令。但是,捕获TestExc2的catch子句的异常表条目确实覆盖了偏移量11处的返回指令。因此,如果嵌套catch语句的return指令抛出异常,将由外出的catch语句处理。

3.13 编译finally

  这节假设编译器在50.0或者更低的版本下生成的class文件,这样jsr指令可能被用到。见4.10.2.5

  try-finally语句的编译和try-catch的编译时相似的。在执行完try代码块之前(无论有没有抛出异常),finally语句块的内容都将被执行。看一个简单的例子:

void tryFinally() {
try {
tryItOut();
} finally {
wrapItUp();
}
}

  编译后的代码:

Method void tryFinally()
aload_0 // Beginning of try block
invokevirtual # // Method Example.tryItOut()V
jsr // Call finally block
return // End of try block
astore_1 // Beginning of handler for any throw
jsr // Call finally block
aload_1 // Push thrown value
athrow // ...and rethrow value to the invoker
astore_2 // Beginning of finally block
aload_0 // Push this
invokevirtual # // Method Example.wrapItUp()V
ret // Return from finally block
Exception table:
From To Target Type
any

  控制传递到try语句之外有四种方法:执行完代码块,返回,执行break或continue语句,或者引发异常。 如果tryItOut返回而没有引发异常,则使用jsr指令将控制转移到finally块。 索引4处的jsr 14指令对索引14处的finally块的代码进行“子程序调用”(finally块被编译为嵌入式子例程)。 当finally块完成时,ret 2指令将控制返回到索引4处的jsr指令之后的指令。

  更详细地说,子例程调用的工作原理如下:jsr指令在跳转之前将下一条指令的地址(在索引7处返回)推送到操作数堆栈上。 作为跳转目标的astore_2指令将操作数堆栈上的地址存储到本地变量2中。运行finally块的代码(在本例中为aload_0和invokevirtual指令)。 假设该代码的执行正常完成,则ret指令从局部变量2中检索地址并在该地址处继续执行。 执行返回指令,tryFinally正常返回。

  更详细地说,子例程调用的工作原理如下:jsr指令在跳转之前将下一条指令的地址(在索引7处返回)推送到操作数堆栈上。 作为跳转目标的astore_2指令将操作数堆栈上的地址存储到本地变量2中。运行finally块的代码(在本例中为aload_0和invokevirtual指令)。 假设该代码的执行正常完成,则ret指令从局部变量2中检索地址并在该地址处继续执行。 执行返回指令,tryFinally正常返回。

  编译包含catch和finally语句的try语句会更复杂一点:

void tryCatchFinally() {
try {
tryItOut();
} catch (TestExc e) {
handleExc(e);
} finally {
wrapItUp();
}
}

  编译为:

Method void tryCatchFinally()
aload_0 // Beginning of try block
invokevirtual # // Method Example.tryItOut()V
goto // Jump to finally block
astore_3 // Beginning of handler for TestExc;
// Store thrown value in local var
aload_0 // Push this
aload_3 // Push thrown value
invokevirtual # // Invoke handler method:
// Example.handleExc(LTestExc;)V
goto // This goto is unnecessary, but was
// generated by javac in JDK ..
jsr // Call finally block
return // Return after handling TestExc
astore_1 // Beginning of handler for exceptions
// other than TestExc, or exceptions
// thrown while handling TestExc
jsr // Call finally block
aload_1 // Push thrown value...
athrow // ...and rethrow value to the invoker
astore_2 // Beginning of finally block
aload_0 // Push this
invokevirtual # // Method Example.wrapItUp()V
ret // Return from finally block
Exception table:
From To Target Type
Class TestExc
any

  如果try语句正常完成,则索引4处的goto指令跳转到索引16处finally块的子例程调用。执行索引26处的finally块,控制返回索引19处的返回指令,并且tryCatchFinally正常返回。

  如果tryItOut抛出TestExc的实例,则选择异常表中的第一个(最上层的)适用的异常处理程序来处理异常。 从索引7开始,该异常处理程序的代码将抛出的值传递给handleExc,并且在其返回时对索引26处的finally块进行相同的子例程调用,如同正常情况一样。 如果handleExc没有抛出异常,则tryCatchFinally会正常返回。

  如果tryItOut抛出的值不是TestExc的实例,或者如果handleExc本身抛出异常,则该条件由异常表中的第二个条目处理,该条件处理索引0和16之间抛出的任何值。该异常处理程序将控制转移到 索引20,其中抛出的值首先存储在局部变量1中。索引26处的finally块的代码被称为子例程。 如果返回,则从局部变量1检索抛出的值,并使用athrow指令重新抛出。 如果在执行finally子句期间抛出新值,则finally子句将中止,并且tryCatchFinally异常返回,将新值抛给其调用者。

3.14 同步

  java虚拟机是用过monitor的进入和退出来实现同步的,不管是显示(使用monitorenter和monitorexit指令)或者隐式(通过方法调用和return指令)。

  java编程语言编写的代码,可能最通用的同步形式就是同步方法。同步方法不是通过monitorenter和monitorexit简单实现。而是简单通过运行时常量池中的ACC_SYNCHRONIZED标识来区分,通过方法调用指令来检查这个标识。

  monitorenter和monitorexit指令用同步代码块的编译。例如:

void onlyMe(Foo f) {
synchronized(f) {
doSomething();
}
}

  编译为:

Method void onlyMe(Foo)
aload_1 // Push f
dup // Duplicate it on the stack
astore_2 // Store duplicate in local variable
monitorenter // Enter the monitor associated with f
aload_0 // Holding the monitor, pass this and...
invokevirtual # // ...call Example.doSomething()V
aload_2 // Push local variable (f)
monitorexit // Exit the monitor associated with f
goto // Complete the method normally
astore_3 // In case of any throw, end up here
aload_2 // Push local variable (f)
monitorexit // Be sure to exit the monitor!
aload_3 // Push thrown value...
athrow // ...and rethrow value to the invoker
return // Return in the normal case
Exception table:
From To Target Type
any
any

  编译器确保在任何方法调用完成时,将对自方法调用以来执行的每个monitorenter指令执行monitorexit指令。 无论方法调用是正常完成(第2.6.4节)还是异常完成(第2.6.5节),都是如此。 为了在方法异常调用完成时保证monitorenter和monitorexit指令的配对,编译器会生成异常处理程序(第2.10节),它将匹配任何异常并且其相关代码执行必要的monitorexit指令。

3.15 注解

  4.7.16-4.7.22节中给出了注解在class文件中表示。这些章节中明确描述了在class文件格式中,如何描述修饰类型,字段和方法的注解。这里只描述关于注解的一些额外规则。

  当编译器遇到必须在运行时可用的带注释的包声明时,它会生成一个具有以下属性的class文件:

  • class文件表示的是一个接口,那么ACC_INTERFACE和ACC_ABSTRACT标识在ClassFile的结构中被设置
  • 如果class文件的版本号低于50.0,然后ACC_SYNTHETIC标识不会设置;如果版本号大于等于50.0,那么ACC_SYNTHETIC标识被设置。
  • 接口具有包访问权限。
  • 接口的名称是package-name.package-info的内部形式。
  • 这个接口没有父类
  • 接口的唯一成员是Java语言规范Java SE 8 Edition(JLS§9.2)所暗示的成员。
  • 声明上的注释在ClassFile结构的属性表中存储为RuntimeVisibleAnnotations和RuntimeInvisibleAnnotations属性。

java虚拟机规范(se8)——java虚拟机的编译(四)的更多相关文章

  1. java虚拟机规范(se8)——java虚拟机结构(一)

    本文翻译自:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html 第二章 虚拟机结构 本文档描述了一个抽象的虚拟机规范,并不描述 ...

  2. java虚拟机规范(se8)——java虚拟机结构(二)

    2.5 运行时数据区域 java虚拟机定义了多个用于程序执行期间的运行时数据区域.这些数据区域中一些随着java虚拟机的启动而创建,随着虚拟机的退出而销毁.其他的数据区域时和线程相关的.线程相关数据区 ...

  3. java虚拟机规范(se8)——java虚拟机结构(六)

    2.11 指令集简介 java虚拟机指令由一个字节的操作码,接着时0个或多个操作数组成,操作码描述了执行的操作,操作数提供了操作所需的参数或者数据.许多指令没有操作数只包含一个操作码. 如果忽略异常处 ...

  4. java虚拟机规范(se8)——java虚拟机的编译(一)

    本文翻译自:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html 第三章 java虚拟机的编译 java虚拟机是设计用来支持ja ...

  5. java虚拟机规范(se8)——java虚拟机的编译(三)

    3.6 接受参数 如果n个参数传给一个实例的方法,按照约定,它们被接受并放在这个新方法创建的栈帧中的局部变量表里,在局部变量表中的序号从1到n.这些参数按照它们传递过来的顺序存放.例如: int ad ...

  6. java虚拟机规范(se8)——java虚拟机的编译(二)

    3.3 算术运算 java虚拟机通常在操作数栈上进行算术运算(例外情况是iinc指令,它直接增加一个局部变量的值).例如下面的align2grain()方法,它的作用是将int值对齐到2的指定次幂: ...

  7. java虚拟机规范(se8)——java虚拟机结构(四)

    2.7 对象的表示 java虚拟机并不要求对象满足任何特定的内部结构. 在Oracle的一些Java虚拟机实现中,对类实例的引用是指向句柄的指针,该句柄本身是一对指针:一个指向包含对象方法的表和指向表 ...

  8. java虚拟机规范(se8)——java虚拟机结构(三)

    2.6. 栈帧 栈帧用于存储数据和部分结果,同样也用于执行动态链接,返回方法的值和分派异常. 当方法被调用的时候会创建一个新的栈帧.当一个方法调用结束时,它对应的栈帧就被销毁了,不管是正常调用结束还是 ...

  9. java虚拟机规范(se8)——java虚拟机结构(五)

    2.10 异常 java虚拟机中的异常用Throwable类或者它的子类的实例来表示.抛出一个异常会导致立即非本地(an inmediate nolocal)的控制转移,从发生异常的地方跳到处理异常的 ...

随机推荐

  1. ES6——字符串

    1.多了两个方法       1)startsWith       2)endsWith 2.模板字符串(`..`)—— 方便字符串连接   `反单引号        1)可以直接把表达式塞进去 &a ...

  2. ubuntu开发c/c++帮助文档

    1.C语言库函数基本的帮助文档 sudo apt-get install manpages sudo apt-get install manpages-de sudo apt-get install ...

  3. 解决图片插入word文档后清晰度降低的问题

    解决图片插入word文档后清晰度降低的问题 在默认情况下,word程序会自动压缩插入word文档中的图片以减小整个word文档的.当我们需要插入word文档中的图片保持原始清晰度时,可以通过设置wor ...

  4. Codeforces Round #554 (Div. 2) C.Neko does Maths (gcd的运用)

    题目链接:https://codeforces.com/contest/1152/problem/C 题目大意:给定两个正整数a,b,其中(1<=a,b<=1e9),求一个正整数k(0&l ...

  5. [NOIP2017普及组]跳房子(二分,单调队列优化dp)

    [NOIP2017普及组]跳房子 题目描述 跳房子,也叫跳飞机,是一种世界性的儿童游戏,也是中国民间传统的体育游戏之一. 跳房子的游戏规则如下: 在地面上确定一个起点,然后在起点右侧画 nn 个格子, ...

  6. springboot打包成jar文件无法正常运行,解决办法已经找到

    1.用intellij idea 创建了一个springboot的项目,前期都运行的好好的,在ide中可以正常运行,但是打包成Jar运行却一直报错. 2.经过不懈探索,终于找到解决办法 3.首先,找到 ...

  7. 给mysql一百万条数据的表添加索引

    直接alter table add index 添加索引,执行一个小时没反应,并且会导致锁表:故放弃该办法,最终解决办法如下: 一.打开mysql 命令行客户端 这里我们那可以看到导出的数据文件所存放 ...

  8. 3.自定义返回json格式的数据给前台(自定义Controller类中的Json方法)

    在mvc的项目中,我们前台做一些操作时,后台要返回一些结果给前台,这个时候我们就需要有一个状态来标识到底是什么类型的错误, 例如: 执行删除的时候,如果操作成功(1行受影响),我们需要返回状态为1并输 ...

  9. 人生苦短_我用Python_def(函数)_004

    # coding=utf-8 # function函数:内置函数 # 例如: len int extent list range str # print insert append pop rever ...

  10. Halo(十)

    Spring Converter(转换器) @FunctionalInterface public interface Converter<S, T> { //一对一转换 @Nullabl ...