之前看过一篇关于return和finally运行顺序的文章。仅在Java的语言层面做了分析。事实上我倒认为直接看bytecode可能来的更清晰一点。
近期一直在看Java虚拟机规范。发现直接分析bytecode更能加深对Java语言的理解。
先看一个仅仅有try-finally。没有catch的样例。
try - finally
public class ExceptionTest { public void tryFinally() { try { tryItOut(); } finally { wrapItUp(); } } // auxiliary methods public void tryItOut() { } public void wrapItUp() {} }
通过javap -c ExceptionTest
来查看它的字节码。
public void tryFinally(); Code: 0: aload_0 1: invokevirtual #2 // Method tryItOut:()V 4: aload_0 5: invokevirtual #3 // Method wrapItUp:()V 8: goto 18 11: astore_1 12: aload_0 13: invokevirtual #3 // Method wrapItUp:()V 16: aload_1 17: athrow 18: return Exception table: from to target type 0 4 11 any
假设没有抛出异常,那么它的运行顺序为
0: aload_0 1: invokevirtual #2 // Method tryItOut:()V 4: aload_0 5: invokevirtual #3 // Method wrapItUp:()V 18: return
假设抛出了异常,JVM会在
Exception table: from to target type 0 4 11 any
中进行控制跳转。假设是位于0到4字节之间的命令抛出了不论什么类型(any type)的异常,会跳转到11字节处继续执行。
11: astore_1 12: aload_0 13: invokevirtual #3 16: aload_1 17: athrow
astore_1会把抛出的异常对象保存到local variable数组的第二个元素。
以下两行指令用来调用成员方法wrapItUp
。
12: aload_0 13: invokevirtual #3
最后通过
16: aload_1 17: athrow
又一次抛出异常。
通过以上分析能够得出结论
在try-finally中,try块中抛出的异常会首先保存在local variable中。然后运行finally块,运行完成后又一次抛出异常。
假设我们把代码改动一下。在try块中直接return。
try - return - finally
public void tryFinally() { try { tryItOut(); return; } finally { wrapItUp(); } }
”反汇编“一下:
0: aload_0 1: invokevirtual #2 // Method tryItOut:()V 4: aload_0 5: invokevirtual #3 // Method wrapItUp:()V 8: return 9: astore_1 10: aload_0 11: invokevirtual #3 // Method wrapItUp:()V 14: aload_1 15: athrow
能够看出finally块的代码仍然被放到了return之前。
假设try块中有
return statement
。一定是finally中的代码先运行,然后return。
JVM规范是这么说的
Compilation of a try-finally statement is similar to that of try-catch. Pior to transferring control outside the try statement, whether that transfer is normal or abrupt, because an exception has been thrown, the finally clause must first be execute.
try - catch - finally
给上面的代码加一个catch块
public void tryCatchFinally() { try { tryItOut(); } catch (TestExc e) { handleExc(e); } finally { wrapItUp(); } }
javap一下
public void tryCatchFinally(); Code: 0: aload_0 1: invokevirtual #2 4: aload_0 5: invokevirtual #3 8: goto 31 11: astore_1 12: aload_0 13: aload_1 14: invokevirtual #5 17: aload_0 18: invokevirtual #3 21: goto 31 24: astore_2 25: aload_0 26: invokevirtual #3 29: aload_2 30: athrow 31: return Exception table: from to target type 0 4 11 Class TestExc 0 4 24 any 11 17 24 any
通过Exception table能够看出:
- catch监听 0 ~ 4 字节类型为
TextExc
的异常。 - finally为 0 ~ 4 以及 11 ~ 17 字节不论什么类型的异常。
也就说 catch block 本身也在 finally block 的管辖范围之内。假设 catch block 中有 return statement,那么也一定是在 finally block 之后运行。
查看原文 http://www.liangfeizc.com/blog/article/32/