java finally块执行时机分析
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块执行时机分析的更多相关文章
- Java中static块执行时机
Java中static块执行时机 演示例子 在使用static进行初始化的操作,怎么也执行不了!代码如下: public class StaticDemo { public static final ...
- java代码块执行顺序
父类 public class Father { public Father() { System.out.println("父类构造PUBLIC father"); } stat ...
- java的static块执行时机
一.误区:简单认为JAVA静态代码块在类被加载时就会自动执行.证错如下: class MyClass1 { static {//静态块 System.out.println("static ...
- java的static块执行时机<转>
一.误区:简单认为JAVA静态代码块在类被加载时就会自动执行.证错如下: class MyClass1 { static {//静态块 System.out.println("static ...
- java static代码块执行时机
之前一直认为static块是在class load的时候执行,今天在验证Spring初始化Context loader的时候,发现bean的static块并没有执行. Java代码: 1 Class ...
- java初始化块执行顺序
java中初始化块的执行顺序在构造器之前,多个初始化块之间定义在前的先执行.如下: public class InitialBlockTest { // The first one { System. ...
- Java中static代码块,{}大括号代码块,构造方法代码块执行顺序!
注:下列代码中的注释都是JUnit4单元测试运行结果. 首先,没有父类的(父类是Object)的类A package Static.of; public class A { { System.out. ...
- Java静态方法块、非静态方法块、构造方法、静态方法执行顺序
示范类StaticTest.java public class StaticTest { {//只有当创建对象的时候执行 System.out.println("H1 ...
- java 代码块的执行顺序
举一个实例程序: class HelloA { public HelloA(){ System.out.println("Hello A!父类构造方法"); } { System. ...
随机推荐
- OpenCV中基于HOG特征的行人检测
目前基于机器学习方法的行人检测的主流特征描述子之一是HOG(Histogram of Oriented Gradient, 方向梯度直方图).HOG特征是用于目标检测的特征描述子,它通过计算和统计图像 ...
- Chart.js报告
引进需要Chart.js <%@ page language="java" pageEncoding="UTF-8"%> <!DOCTYPE ...
- Windows下程序打包发布时的小技巧(使用Dependency Walker侦测不理想,改用VS自带的dumpbin则万无一失,还可查看dll导出的函数)
Windows下开发的应用程序在发布时,需要将其依赖的一些动态链接库一起打进安装包里面去.这个时候,快速确定这个程序到底依赖哪些动态链接库变得非常重要.很久以前写过一篇关于Qt程序安装包制作的博客,里 ...
- WPF 绑定父类属性
原文:WPF 绑定父类属性 1.绑定父控件的属性. <ContextMenu x:Key="ContextMenuColoum"> <MenuItem Heade ...
- jquery 隐私迭代
<!DOCTYPE html><html><head><meta http-equiv="Content-Type" content=&q ...
- jquery动态创建小广告
<!DOCTYPE html><html><head><meta http-equiv="Content-Type" content=&q ...
- Delphi中返回类型为string的函数的一个陷阱(不是很懂)
如果类的一个成员函数的返回值是string类型,需要注意一个问题 其返回值可能是错误的 例如函数的实现如下 function GetString( s: string ): string;begin ...
- WPF滚动条嵌套,响应鼠标滑轮事件的处理
在C# 中,两个ScrollViewer嵌套在一起或者ScrollViewer里面嵌套一个ListBox.Listview(控件本身有scrollviewer)的时候,我们本想要的效果是鼠标滚动整个S ...
- Visual Studio更改编码格式为“UTF-8”
原文:Visual Studio更改编码格式为"UTF-8" 用VS2015新建了个Python文件,在VS2015打开时中文显示正常, 用Visual Studio Code文本 ...
- oracle 12c连接pdb
12c中,如何连接pluggable database: 使用默认的service连接pdb,创建pdb之后,在监听中自动添加以pdb为名的service: 用户在cluster中创建service, ...