java里 finally 关键字通常与try catch块一起使用。用来在方法结束前或发生异常时做一些资源释放的操作。最近也看到网上有一些讨论try catch finally关键词执行的顺序的文章,并给出了finally块是在方法最后执行的。

这些观点普遍认为:

1)finally关键词是在程序return语句后返回上一级方法前执行的,其中返回值会保存在一个临时区域,待执行完finally块的部分后,在将临时区域的值返回。

2)若finally块里有返回值会替换掉程序中前面的try 或catch块中return语句存放在临时区域的值。

但是问题真的是这样的吗,我们仔细的想想,jvm是在运行时对字节码指令进行解释执行的,当他在执行到return语句后,他哪知道后面有没有finally块,如果没有finally块怎么办,不管是字节码指令还是计算机的指令应该是明确的,jvm没有那么智能,同一个指令必须是明确的,不会包含两层含义。所以对于return语句在运行时不管什么情况,统一会弹出栈的内容并返回到调用方法。

与此同时,我们可以看到《深入java虚拟机》这一本书中给出了另外一种解释。在java编译器编译finally子句时会生成jsr指令,它使jvm调转到微型子例程进行执行,也就是finally块处,同时将程序中的return 0语句编译为在调用jsr指令前栈中的返回变量到局部变量,调用jsr指令,执行finally块,finally块返回,在将局部变量中的返回值压入栈,执行ireturn指令,从栈中弹出返回值,返回到调用方法,这里在执行jsr指令前将返回值保存在局部变量中,是因为finally块执行的过程中可能发生异常或者说是也有返回值,只有这样做才能保证最后程序执行的一致性。由于《深入java虚拟机》写的已经也一些年代了,同时作者使用的jvm编译器的实现及版本与本文讨论的也有差别。所以经过测试,对于同一程序不同的编译器实现或版本不同的字节码的生成稍微有些差别。有兴趣可以看看这本书中finally子句生成的字节码。

本文的字节码生成使用的是Oracle的jdk8u-25版本的编译器编译生成的。

下面我们来看一个实例。

1.try catch finally 示例:

public class FinallyTest {
public static void main(String[] args) { int r = test();
System.out.println(r); }
public static int test()
{
try {
System.out.println("try");
//return 1/0;
return 0;
} catch (Exception e) {
System.out.println("exception");
return 100;
}finally{
System.out.println("finally"); } } }

try块中使用return 0语句,程序的运行结果是:

try
finally
0

try块中使用 return 1/0 语句,程序运行的结果是:

exception
finally
100

其实通过运行结果我们可以看出的是finally块是在try或catch块中的return语句前其他语句后执行的。也就是说程序的书写顺序与我们执行顺序不符,因为jvm是对字节码进行解释执行的,那么我们需要看看java编译器是如何编译这段代码的,看看其生成的字节码究竟是什么样的。

2.程序生成的部分字节码:(java字节码指令请参考

  public static int test();
descriptor: ()I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=0
0: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #36 // String try
5: invokevirtual #38 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream;
11: ldc #41 // String finally
13: invokevirtual #38 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
16: iconst_0
17: ireturn
18: astore_0
19: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream;
22: ldc #43 // String exception
24: invokevirtual #38 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
27: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream;
30: ldc #41 // String finally
32: invokevirtual #38 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
35: bipush 100
37: ireturn
38: astore_1
39: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream;
42: ldc #41 // String finally
44: invokevirtual #38 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
47: aload_1
48: athrow
Exception table:
from to target type
0 8 18 Class java/lang/Exception
0 8 38 any
18 27 38 any

从红色的部分我们可以看出:10,11行对应的是finally块语句指令,16,17对应的是return 0指令,在try块其他语句之后,return之前。而19,20对应的是finally块指令,21,22对应的是return 100语句的指令,在catch其他语句之后,return之前,由此我们可以看出这些背后发生的一切是java编译器为我们做了这一切,至于程序中发生的异常,jvm会从异常表找到对应处理异常的地址位置执行。

因此我们可以得出结论finally块中的语句会由java编译器插入到try块和catch块return语句之前,其他语句之后。在这里也没有生成jsr调用的子例程。所以才发生不管是执行try块还是执行catch块,最终在方法返回前都会执行finally块。

java finally块执行时机分析的更多相关文章

  1. Java中static块执行时机

    Java中static块执行时机 演示例子 在使用static进行初始化的操作,怎么也执行不了!代码如下: public class StaticDemo { public static final ...

  2. java代码块执行顺序

    父类 public class Father { public Father() { System.out.println("父类构造PUBLIC father"); } stat ...

  3. java的static块执行时机

    一.误区:简单认为JAVA静态代码块在类被加载时就会自动执行.证错如下: class MyClass1 { static {//静态块 System.out.println("static  ...

  4. java的static块执行时机<转>

    一.误区:简单认为JAVA静态代码块在类被加载时就会自动执行.证错如下: class MyClass1 { static {//静态块 System.out.println("static  ...

  5. java static代码块执行时机

    之前一直认为static块是在class load的时候执行,今天在验证Spring初始化Context loader的时候,发现bean的static块并没有执行. Java代码: 1 Class ...

  6. java初始化块执行顺序

    java中初始化块的执行顺序在构造器之前,多个初始化块之间定义在前的先执行.如下: public class InitialBlockTest { // The first one { System. ...

  7. Java中static代码块,{}大括号代码块,构造方法代码块执行顺序!

    注:下列代码中的注释都是JUnit4单元测试运行结果. 首先,没有父类的(父类是Object)的类A package Static.of; public class A { { System.out. ...

  8. Java静态方法块、非静态方法块、构造方法、静态方法执行顺序

    示范类StaticTest.java public class StaticTest {     {//只有当创建对象的时候执行         System.out.println("H1 ...

  9. java 代码块的执行顺序

    举一个实例程序: class HelloA { public HelloA(){ System.out.println("Hello A!父类构造方法"); } { System. ...

随机推荐

  1. Qt - QDialog,QWidget实现模态及非模态(模态Widget不能有父窗口,如果设置无边框就不能阻塞父窗口,但是可以强行设置指定Qt::Dialog,还可以setAttribute(Qt::WA_ShowModal),很多讲究)good

    在Qt中QDialog为“窗口”,而QWidget为“部件”,首先还是了解下<Qt 窗口与部件的概念>. 对于 QDialog 的模态及非模态是直接可以实现的,很多课本中都会提到,此处总结 ...

  2. 制作一个简单的WPF图片浏览器

    原文:制作一个简单的WPF图片浏览器 注:本例选自MSDN样例,并略有改动.先看效果: 这里实现了以下几个功能:1.  对指定文件夹下所有JPG文件进行预览2.  对选定图片进行旋转3.  对选定图片 ...

  3. ValueStack和OGNL达到Struts2形式的数据存储原理

    (1)最近学习struts相框,我们在快乐struts强大.为了便于使用转发,但不了解详细的内部数据存储: (2)网上找了很多关于struts数据存储的原理,但我还没有找到一个具体的解释,本书上找到了 ...

  4. 《Planet Earth II》观看笔记

    carrion:n. 腐肉:臭尸:不洁之物 cub:n. 幼兽:不懂规矩的年轻人:chick; n. 小鸡:小鸟:少妇 herd:兽群: 1. 高频单词 terrain:n. [地理] 地形,地势:领 ...

  5. HDU 4414 Finding crosses(dfs)

    Problem Description The Nazca Lines are a series of ancient geoglyphs located in the Nazca Desert in ...

  6. ORACLE 11G在相同的linuxserver从实施例1满库到实例2上

    早期的导出命令: [root@powerlong4 ~]# su - oracle [oracle@powerlong4 ~]$ export ORACLE_SID=pt1; [oracle@powe ...

  7. 专访黄勇:Java在未来的很长一段时间仍是主流(把老板当情人,把同事当小孩,把客户当病人)

    url:http://www.csdn.net/article/2015-09-06/2825621 2015-09-06 13:18 摘要:本文采访了现任阿里巴巴公司系统架构师黄勇,从事近十年的Ja ...

  8. OpenGL(十四) 模板测试

    启用模板测试时,OpenGL会在内存中开辟一块空间作为模板缓冲区,里边保存了每个像素的"模板值",模板测试的过程就是把每一个像素的模板值与一个设定的模板参考值进行比较,符合设定条件 ...

  9. [GEiv]第七章:着色器 高效GPU渲染方案

    第七章:着色器 高效GPU渲染方案 本章介绍着色器的基本知识以及Geiv下对其提供的支持接口.并以"渐变高斯模糊"为线索进行实例的演示解说. [背景信息] [计算机中央处理器的局限 ...

  10. 每日一题:Java异常处理

    什么是异常 在理想情况下,程序总会运行在很完美的环境中,网络不会终端,文件一定存在,程序不会有 BUG.但是,理想很丰满,现实很骨干,实际生产环境中,网络可能会中断,文件可能会找不到,内存可能会溢出, ...