`
DLevin
  • 浏览: 37452 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

finally知多少(二)

    博客分类:
  • Tips
阅读更多

接:finally知多少(一)

问题解释

结合《深入Java虚拟机(第二版)》这本书和代码编译后产生的二进制指令代码,我对以上问题做了部分解释,鉴于我的才疏学浅,有些观点是有误的,希望高手指正(有误的观点容易引起误导,这也是所以我一直非常小心,奈何水平有限,有些时候难免出错)。

 

在《深入Java虚拟机(第二版)》的第18章中提到,在早期的Java中,finally的行为是通过JSR指令来实现的,并且为这个指令引入了微型子程序的概念。我的理解,所谓微型子程序就是在函数A中嵌入一个不完整的函数B的调用。比如在这本书上的一个例子:

	private static int microSubroutine(boolean bValue) {
		try {
			if(bValue) {
				return 1;
			}
			return 0;
		} finally {
			System.out.println("finally");
		}
	}

 

会生成以下的二进制代码:

 0 iload_0

 1 ifeq 11

 4 iconst_1

 5 istore_1

 6 jsr 24

 9 iload_1

10 ireturn

11 iconst_0

12 istore_1

13 jsr 24

16 iload_1

17 ireturn

18 astore_2

19 jsr 24

22 aload_2

23 athrow

 

24 astore_3

25 getstatic #7 <Field java.io.PrintStream out>

28 ldc #1 <String “finally”>

30 invokevirtual #8 <Method void println(java.lang.String)>

33 ret 3

 

如上,24前缀的代码行以后的部分就是微型子程序,在每一个出口之前都会用JSR调用这个微型子例程序,在这个微型子例程序返回(ret)后,返回调用JSR指令的下一条指令,然后返回(ireturnathrow)。

jsr指令和ret指令的格式如下:

jsr    branchbyte1, branchbyte2

把返回地址压栈,跳转至((branchbyte1<<8) | branchbyte2)的位置继续之行。

ret index

返回在index指示的局部变量中存储的值(位置)。

 

在上面的二进制代码中,每次通过jsr 24跳转到微型子程序,它先将返回地址(jsr 24指令的下一条指令的地址)保存在index3的局部变量中,执行完微型子程序后,通过ret 3返回到调用jsr 24指令的下一条指令执行,并最终执行返回。

 

可是后来(有人说是自1.4.2后),JVM中取消了jsr指令了,所有finally内部的代码都内联到源代码中了(二进制的源代码)。所以以上的代码在之后的编译器中会产生如下的二进制代码:

     0  iload_0 [bValue]

     1  ifeq 14

     4  getstatic java.lang.System.out : java.io.PrintStream [16]

     7  ldc <String "finally"> [94]

     9  invokevirtual java.io.PrintStream.println(java.lang.String) : void [24]

    12  iconst_1

13  ireturn

 

    14  getstatic java.lang.System.out : java.io.PrintStream [16]

    17  ldc <String "finally"> [94]

    19  invokevirtual java.io.PrintStream.println(java.lang.String) : void [24]

    22  iconst_0

23  ireturn

 

    24  astore_1

    25  getstatic java.lang.System.out : java.io.PrintStream [16]

    28  ldc <String "finally"> [94]

    30  invokevirtual java.io.PrintStream.println(java.lang.String) : void [24]

    33  aload_1

    34  athrow

 

额,貌似有点偏题了,以上的描述是为了解释《深入Java虚拟机(第二版)》中对finally描述过时的描述。下面让我们来真正的解决这个问题。还是从生成的Java二进制代码入手。

 

首先来看一下valueChangeInFinallyTest()函数的二进制代码(注释了打印语句,使代码简洁):

         //int i = 10

     0  bipush 10

     2  istore_0 [i]

       //int j = 1

     3  iconst_1

     4  istore_1 [j]

       //i = 100

     5  bipush 100

     7  istore_0 [i]

       //j = 2

     8  iconst_2

     9  istore_1 [j]

       //保存i的值,因为它是要返回的

    10  iload_0 [i]

11  istore 4

//--------------------------------内联finally语句块(开始)----------------------

//i = 1000

    13  sipush 1000

16  istore_0 [i]

//j = 3

    17  iconst_3

18  istore_1 [j]

//--------------------------------内联finally语句块(结束)----------------------

//加载保存后的i的值,并返回。这里返回的是finally语句块执行前的i(由istore 4语句缓存起来)的值,因而在finally语句块中任何对i的操作并不会保留下来。这是在没有异常发生的情况下。

    19  iload 4

21  ireturn

 

    22  astore_2 [e]

    23  aload_2 [e]

24  invokevirtual java.lang.Exception.printStackTrace() : void [104]

//--------------------------------内联finally语句块(开始)----------------------

    27  sipush 1000

    30  istore_0 [i]

    31  iconst_3

32  istore_1 [j]

//--------------------------------内联finally语句块(结束)----------------------

33  goto 45

 

36  astore_3

//--------------------------------内联finally语句块(开始)----------------------

    37  sipush 1000

    40  istore_0 [i]

    41  iconst_3

42  istore_1 [j]

//--------------------------------内联finally语句块(结束)----------------------

//而在异常发生但没有被正确处理的情况下,返回值已经没有什么意义了。

    43  aload_3

44  athrow

 

//这里是在有异常发生,并且异常得到了正确处理的情况下返回的,此时在finally语句块中对i的操作就会保存下来,并返回给调用者。

    45  iload_0 [i]

    46  ireturn

相信以上的注释已经能很好的的解决这个问题了(注:这里j的存在是为了证明在内联finally语句块的时候,它只缓存返回值i,而无须缓存其他变量的值,如j的值)。需要特别注意的一点是,如果正常返回的话,finally语句块中修改i的值是保存不下来的,但是如果出现异常,并被正常捕获后,在finally语句块中修改的i的值就会保存下来了。

 

那么对valueChangeReturnInFinallyTest()函数中的现象如何解释呢?对这个问题解释,首先要理解ireturn的指令。ireturn指令没有操作数,它把当前操作栈的栈顶的int值作为默认的操作数。ireturn指令会弹出当前栈顶的int值,将其压入调用者的操作栈中,同时忽略当前操作栈中的其他值,即函数正常返回。因而如果在不优化的情况下,在finally语句块中的return语句会返回当前栈顶的int值(修改后的i值),然后函数返回,此时栈上的其他操作数就被忽略了,并且原本应该执行的ireturn语句也不会之行了。这种方式甚至会忽略抛出的异常,即使当前方法有异常抛出,它的调用方法还是认为它正常返回了。

如果查看优化后的valueChangeReturnInFinallyTest()方法的二进制源码后,会发现当前的代码更加简洁了。但是它还是没有避免在finally语句块中使用return后,会忽略没有捕获到的异常的问题。

         //int i = 10

     0  bipush 10

     2  istore_0 [i]

       //int j = 1

     3  iconst_1

     4  istore_1 [j]

       //i = 100

     5  bipush 100

     7  istore_0 [i]

       //j = 2

     8  iconst_2

     9  istore_1 [j]

10  goto 22

//catch block

    13  astore_2 [e]

    14  aload_2 [e]

    15  invokevirtual java.lang.Exception.printStackTrace() : void [104]

    18  goto 22

21  pop

//--------------------------------内联finally语句块(开始)----------------------

//i = 100

    22  sipush 1000

25  istore_0 [i]

//j = 3

    26  iconst_3

27  istore_1 [j]

//--------------------------------内联finally语句块(结束)----------------------

//返回finally语句块中i的值

    28  iload_0 [i]

    29  ireturn

经过以上的解释,我想对refValueChangeInFinallyTest()函数中的现象就比较好解释了,因为当进入finally语句块的时候,保存的只是Person实例的一个引用,在finally语句块中依然可以通过引用操作Person内部成员的,因而在finally语句块中的修改才能保存下来。

 

而进过编译器优化后的finallyReturnTest()finallyBreakTest()函数生成的二进制代码就成一样的了:

     0  iload_0 [value]

     1  ifeq 8

     4  goto 8

     7  pop

     8  iconst_0

     9  ireturn

 

后记

原本以为这是一个小问题的,没想到花了我一个下午的时间才把问题说清楚了,而在描述问题的过程中,我对问题的本质也看的更加清晰了。这个问题开始是源于我在论坛http://www.iteye.com/topic/458668中看到,感觉论坛里面的人都没很好的说清楚这个问题,刚好我看完了《深入Java虚拟机(第二版)》的书,就把这个问题完整的描述出来了。

分享到:
评论
1 楼 dlutdt 2010-10-13  
不错 ! 顺便问一下楼主 杭州哪里可以买到《深入java虚拟机(第二版)》,小弟最近逛了一些书城都没买到。 很想看 , 楼主如果只情 希望能够告知  多谢了! 小弟邮箱peerjava@163.com

相关推荐

    java程序员必知的

    第二,Anonymous Inner Class (匿名内部类) 是否可以extends(继承)其它类,是否可以implements(实现)interface(接口)?  可以继承其他类或完成其他接口,在swing编程中常用此方式。  第三,Static Nested Class ...

    Android中高级面试必知必会.pdf

    目录 Android 中高级面试必知必会.1 第一章 Java 相关高频面试解析..........3 1. HashMap ......3 2. ArrayList .....25 3.LinkedList.........30 4.Hashset 源码分析........35 5. 内存模型.......47 6. 垃圾回收...

    500道java后端面试必知必会

    2. **异常处理**: 学习如何使用try-catch-finally语句来捕获和处理运行时错误,以及了解不同类型的异常,如Checked异常和Unchecked异常的区别。 3. **内存管理与垃圾回收**: 了解Java的内存模型,包括堆内存、栈...

    2019_2020学年高中英语Module2TrafficJam5SectionⅤWriting知能演练轻松闯关外研版必修4

    3. **词汇运用**:第5句"If you have a job, do devote yourself to it and finally you’ll succeed."强调了副词"do"在强调动词时的用法,同时提醒学生要全身心投入工作才能成功。第6句"Please do me a favor—...

    英语经典表达.docx

    - 这里使用了 “as far as we are aware” 来强调目前所知的信息。 - **方法**:“First of all, we synthesized the modified particles. Then, we used these particles to create the imprinted molecular ...

    2018八年级英语上册Unit8HowdoyoumakeabananamilkshakeSelfCheck知能演练提升新版人教新目标版

    2. `Then/Next/Finally`: 这些词常用于指示步骤顺序,描述做某事的流程。 3. `Do you know how...?`: 你知道怎么... 这是一个询问对方是否知道某个信息的问句结构。 **阅读填词** 1. `eat`: 吃。 2. `add`: 添加。...

    广东省广州市2018年中考英语学科模拟题一20180709282

    "I finally managed __3__ one..." "manage to do sth." 表示成功地做了某事,因此这里应该填"to get",答案是C。 第四题考查物主代词。"...in __4__ new home all the time." 这里的"new home"指的是鸟的新家,...

    2018八年级英语上册Unit8HowdoyoumakeabananamilkshakeSectionB1a_1e知能演练提升新版人教新目标版

    ### 2018 八年级英语上册 Unit8 How do you make a banana milkshake SectionB (1a-1e) 知能演练提升 #### 一、知识点概述 本单元主要介绍了如何制作香蕉奶昔,并通过一系列练习帮助学生掌握与食物相关的词汇以及...

    vb.net.ppt

    VB.NET中的错误处理机制包括Try...Catch...Finally和Throw语句,用于捕获和处理运行时可能出现的异常。 ### 八、调试与性能优化 1. **调试工具**:Visual Studio集成的调试器可以帮助定位和修复代码错误。 2. **...

    Java 开发手册 阿里巴巴公开版本

    方法和变量名小驼峰命名,遵循见名知意的原则。包名全小写,采用反域名的方式。 2. 注释:清晰明了,避免过多无用注释,使用Javadoc规范对类和方法进行注释,单行注释使用//,多行注释使用/* */。 3. 初始化与赋值:...

    鸡啄米:C++编程入门系列之目录和总结

    - try-catch-finally语句的使用。 - 自定义异常类的定义。 ### 总结 通过本系列教程的学习,我们可以系统地掌握C++语言的基础知识,包括但不限于数据类型、运算符、控制结构、函数、类与对象、继承与派生、多态性...

    华为内部代码规范

    1. 变量命名:遵循“见名知意”的原则,使用有意义的单词或词组,避免使用缩写,大小写遵循驼峰命名法或下划线分隔。 2. 注释规范:注释应简洁明了,描述代码功能、目的及修改历史,每行代码的上方都要有相应的注释...

    整理了一个关于网上java问与答的一个手册

    1. 异常概述:介绍Java异常处理机制,包括异常类层次结构和try-catch-finally语句块的使用。 2. 自定义异常:讲解如何创建自定义异常类以及何时使用它们。 四、集合框架 1. 数组列表(ArrayList)与链表...

    java 必会108题,大数据面试文档,Java面试文档,Java公司面试真题

    一、Java必知必会108题 这部分内容可能涵盖了Java基础语法、面向对象编程、集合框架、多线程、异常处理、I/O流、网络编程、反射机制、JVM内存模型等多个方面。例如: 1. Java的基础语法:变量声明、数据类型、运算符...

    A Complete Guide to Programming in C++

    #### 二、基礎知識 ##### 1. C++語言概觀 - **歷史與發展**:C++源自C語言,於1979年由Bjarne Stroustrup在贝尔實驗室開始研發,旨在結合C語言的高效性與面向對象編程的概念。 - **特點**:支持多種編程范式(如面向...

    Java基础学习/高级类/异常处理/线程

    二、Java高级类 随着对Java基础的深入,开发者会接触到更复杂的概念,如抽象类、接口、内部类、匿名类和枚举。抽象类用于提供部分实现,而接口则定义了一组方法,使得类可以多继承。内部类允许在一个类的内部定义另...

    2012高考英语作文完美模板.pdf

    - **时间顺序**:如"now, after, later, finally…",引导读者跟随时间线索理解内容。 - **解释说明**:"for example, in this case…",使论证更具说服力。 - **转折关系**:"but, however, on the contrary…...

Global site tag (gtag.js) - Google Analytics