java裏 finally 關鍵字一般與try catch塊一塊兒使用。用來在方法結束前或發生異常時作一些資源釋放的操做。最近也看到網上有一些討論try catch finally關鍵詞執行的順序的文章,並給出了finally塊是在方法最後執行的。html
這些觀點廣泛認爲:java
1)finally關鍵詞是在程序return語句後返回上一級方法前執行的,其中返回值會保存在一個臨時區域,待執行完finally塊的部分後,在將臨時區域的值返回。jvm
2)若finally塊裏有返回值會替換掉程序中前面的try 或catch塊中return語句存放在臨時區域的值。測試
可是問題真的是這樣的嗎,咱們仔細的想一想,jvm是在運行時對字節碼指令進行解釋執行的,當他在執行到return語句後,他哪知道後面有沒有finally塊,若是沒有finally塊怎麼辦,無論是字節碼指令仍是計算機的指令應該是明確的,jvm沒有那麼智能,同一個指令必須是明確的,不會包含兩層含義。因此對於return語句在運行時無論什麼狀況,統一會彈出棧的內容並返回到調用方法。spa
與此同時,咱們能夠看到《深刻java虛擬機》這一本書中給出了另一種解釋。在java編譯器編譯finally子句時會生成jsr指令,它使jvm調轉到微型子例程進行執行,也就是finally塊處,同時將程序中的return 0語句編譯爲在調用jsr指令前棧中的返回變量到局部變量,調用jsr指令,執行finally塊,finally塊返回,在將局部變量中的返回值壓入棧,執行ireturn指令,從棧中彈出返回值,返回到調用方法,這裏在執行jsr指令前將返回值保存在局部變量中,是由於finally塊執行的過程當中可能發生異常或者說是也有返回值,只有這樣作才能保證最後程序執行的一致性。因爲《深刻java虛擬機》寫的已經也一些年代了,同時做者使用的jvm編譯器的實現及版本與本文討論的也有差異。因此通過測試,對於同一程序不一樣的編譯器實現或版本不一樣的字節碼的生成稍微有些差異。有興趣能夠看看這本書中finally子句生成的字節碼。code
本文的字節碼生成使用的是Oracle的jdk8u-25版本的編譯器編譯生成的。htm
下面咱們來看一個實例。blog
1.try catch finally 示例:ip
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字節碼指令請參考)
1 public static int test(); 2 descriptor: ()I 3 flags: ACC_PUBLIC, ACC_STATIC 4 Code: 5 stack=2, locals=2, args_size=0 6 0: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream; 7 3: ldc #36 // String try 8 5: invokevirtual #38 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 9 8: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream; 10 11: ldc #41 // String finally 11 13: invokevirtual #38 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 12 16: iconst_0 13 17: ireturn 14 18: astore_0 15 19: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream; 16 22: ldc #43 // String exception 17 24: invokevirtual #38 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 18 27: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream; 19 30: ldc #41 // String finally 20 32: invokevirtual #38 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 21 35: bipush 100 22 37: ireturn 23 38: astore_1 24 39: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream; 25 42: ldc #41 // String finally 26 44: invokevirtual #38 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 27 47: aload_1 28 48: athrow 29 Exception table: 30 from to target type 31 0 8 18 Class java/lang/Exception 32 0 8 38 any 33 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塊。