LetsCoding.cn

          天地之間有桿秤,拿秤砣砸老百姓。

          JVM中finally子句介紹

          歡迎來到“Under The Hood”第七期。本期我們介紹JVM處理finally子句的方式及相關字節碼。你可能需要閱讀往期的文章才能更好的理解本文。

          finally子句

          JVM執行Java字節碼時,它有幾種方式可以退出一個代碼塊(花括號中間的語句)。其中之一,就是簡單的執行完其中所有的語句,然后退出代碼塊。第二種,JVM可能會在代碼塊中間的任何一處,遇到像break,continue,return之類的語句,強制它跳出該代碼塊。第三種,JVM可能會在執行過程中,出現了異常,然后它跳轉到匹配的catch子句,或者沒有找到相應的catch子句,直接退出當前線程。由于單個代碼塊有如此多的潛在退出點(exit point),擁有一個簡單的方式來表達“無論代碼塊以什么方式退出,有些事情總能發生”是很值得的。然后就有了try-finally子句。

          try-finally子句的用法:

          • 把擁有多個退出點的代碼塊放在try塊中,并且
          • 把無論try塊怎么退出,始終能被執行的代碼放在finally塊中。

          例如:

          1. try {
          2.     // Block of code with multiple exit points
          3. }
          4. finally {
          5.     // Block of code that is always executed when the try block is exited,
          6.     // no matter how the try block is exited
          7. }

          如果try塊有多個catch子句與之關聯,你就必須把finally子句放在所有catch子句的后面:

          1. try {
          2.     // Block of code with multiple exit points
          3. }
          4. catch (Cold e) {
          5.     System.out.println("Caught cold!");
          6. }
          7. catch (APopFly e) {
          8.     System.out.println("Caught a pop fly!");
          9. }
          10. catch (SomeonesEye e) {
          11.     System.out.println("Caught someone's eye!");
          12. }
          13. finally {
          14.     // Block of code that is always executed when the try block is exited,
          15.     // no matter how the try block is exited.
          16.     System.out.println("Is that something to cheer about?");
          17. }

          在try塊的代碼執行過程中,先由catch子句負責處理拋出的異常,然后再執行finall子句中的代碼。例如,如果上述代碼中的try塊拋出Cold異常,控制臺將會輸出如下信息:

          1. Caught cold!
          2. Is that something to cheer about?

          字節碼中的try-finally子句

          在字節碼中,finally子句扮演著方法中子程序的角色。在try塊和它所關聯的catch子句中的每個退出點,代表finally子句的子程序會被調用。一旦最后一條語句執行完成,且沒有拋出異常,沒有執行return、continue、和break,則finally子句調用結束,子程序返回。JVM從調用子程序的指令后下一條指令繼續執行,這樣try塊就能以適當的方式退出了。

          讓JVM跳轉到子程序的操作碼是jsr指令。jsr指令有2個單字節操作數,它們組成子程序入口地址到jsr跳轉指令的偏移量。jsr_w指令是jsr的變種,功能和jsr相同,但有4個單字節操作數。當JVM遇到jsr或jsr_w指令,它把返回地址(return address,即jsr或jsr_w指令的下一條指令地址)壓入棧中,然后從子程序的入口處繼續往下執行。

          JVM在子程序完成之后,調用ret指令,從子程序返回。ret指令擁有一個操作數,它是一個索引,指向存儲返回地址的本地變量。處理finally子句的操作碼總結如下:

          OPCODE
          OPERAND(S)
          DESCRIPTION
          jsr branchbyte1, branchbyte2 pushes the return address, branches to offset
          jsr_w branchbyte1, branchbyte2, branchbyte3, branchbyte4 pushes the return address, branches to wide offset
          ret index returns to the address stored in local variable index

          不要把子程序和Java里的方法搞混了,Java中的方法會使用不同于子程序的指令集。invokevirtual或invokeonvirtual指令用來處理方法調用,而return,areturn或ireturn指令用來處理方法返回。jsr指令不會導致方法被調用,而會使JVM跳轉到同一方法中不同的指令處。類似的,ret指令不會從方法中返回,它讓JVM從子程序返回到jsr指令的下一條指令處。實現finally子句的字節碼之所以被稱為子程序,是因為它們看起來像是單個方法的字節碼流中的很小的子程序。

          你可能會認為ret指令應該把返回地址從棧中彈出,因為那里是它被jsr指令壓入的地方。但是,你錯了。

          每個子程序的開始,返回地址就被從棧頂彈出,并保存到本地變量中。ret指令會從同一個本地變量獲得子程序的返回地址。這種不對稱的返回地址使用方式是必須的,因為finall子句(子程序)本身可以拋出異常,或者包含return,break或continue語句。由于這種可能性,被jsr指令壓入棧中的返回地址必須立即從棧頂移除,這樣當JVM由于break,continue,return語句或拋出的異常而從finally子句中退出時,返回地址就不會依舊保存在棧中。因此,返回地址在子程序執行的開始,就被保存到本地變量中。

          作為示例,參考下面的代碼,它包含一個擁有break語句的finally子句。無論傳給surpriseTheProgrammer()方法的參數是什么,這段代碼的結果總是false。

          1. static boolean surpriseTheProgrammer(boolean bVal) {
          2.     while (bVal) {
          3.         try {
          4.             return true;
          5.         }
          6.         finally {
          7.             break;
          8.         }
          9.     }
          10.     return false;
          11. }

          上面的例子顯示了,為什么要在子程序的開始處,就把返回地址保存到本地變量中。因為finally子句從break返回,它不會執行ret指令。結果就是JVM不會執行“return true”語句,它會執行break語句,并繼續往下執行,結束while循環,執行“return false”。

          以break語句退出finally子句的方式,跟以return,continue或者拋出異常的方式退出是一樣的。如果finally子句以這四種方式之一退出,子句中的ret指令永遠都不會被執行到。鑒于此,ret指令不能保證肯定會被執行,JVM不能指望它去移除棧中的返回地址。因此,返回地址在子程序執行的開始,就被保存到本地變量中。

          作為一個完整的例子,請看如下方法,它包含一個有2個退出點的try塊。

          1. static int giveMeThatOldFashionedBoolean(boolean bVal) {
          2.     try {
          3.         if (bVal) {
          4.             return 1;
          5.         }
          6.         return 0;
          7.     }
          8.     finally {
          9.         System.out.println("Got old fashioned.");
          10.     }
          11. }

          它的字節碼如下:

          1. // The bytecode sequence for the try block:
          2. 0 iload_0               // Push local variable 0 (arg passed as divisor)
          3. 1 ifeq 11               // Push local variable 1 (arg passed as dividend)
          4. 4 iconst_1              // Push int 1
          5. 5 istore_3              // Pop an int (the 1), store into local variable 3
          6. 6 jsr 24                // Jump to the mini-subroutine for the finally clause
          7. 9 iload_3               // Push local variable 3 (the 1)
          8. 10 ireturn               // Return int on top of the stack (the 1)
          9. 11 iconst_0              // Push int 0
          10. 12 istore_3              // Pop an int (the 0), store into local variable 3
          11. 13 jsr 24                // Jump to the mini-subroutine for the finally clause
          12. 16 iload_3               // Push local variable 3 (the 0)
          13. 17 ireturn               // Return int on top of the stack (the 0)
          14. // The bytecode sequence for a catch clause that catches any kind of exception
          15. // thrown from within the try block.
          16. 18 astore_1              // Pop the reference to the thrown exception, store
          17.                          // into local variable 1
          18. 19 jsr 24                // Jump to the mini-subroutine for the finally clause
          19. 22 aload_1               // Push the reference (to the thrown exception) from
          20.                            // local variable 1
          21. 23 athrow                // Rethrow the same exception
          22. // The miniature subroutine that implements the finally block.
          23. 24 astore_2              // Pop the return address, store it in local variable 2
          24. 25 getstatic #8          // Get a reference to java.lang.System.out
          25. 28 ldc #1                // Push < string "Got old fashioned." > from the constant pool
          26. 30 invokevirtual #7      // Invoke System.out.println()
          27. 33 ret 2                 // Return to return address stored in local variable 2

          try塊的字節碼中含有2個jsr指令,另外一個jsr指令在catch子句中。catch子句是由編譯器自動添加的,因為如果在try塊執行過程中拋出異常,finally塊必須任然被執行。因此catch子句僅僅調用代表finally塊的子程序,然后拋出相同的異常。下面所示的giveMeThatOldFashionedBoolean() 方法的異常表說明,0到17行所拋出的任何異常,都由從18行開始的catch子句來處理。

          FROM
          TO
          TARGET
          TYPE
          0 18 18 any

          可以看出,finally子句的字節碼,以彈出并保存棧頂返回地址到本地變量開始,以從本地變量2中取得返回地址的ret指令返回。

          本文譯自:Try-finally clauses defined and demonstrated

          譯者注:基于字節碼校驗方面的考量,JVM中的jsr/ret指令在Java 6.0時,已經被移除。

          本文出自:碼農合作社 》JVM中finally子句介紹,轉載請注明。

          posted on 2014-06-08 02:42 Rolandz 閱讀(2187) 評論(1)  編輯  收藏 所屬分類: 編程實踐

          評論

          # re: JVM中finally子句介紹 2014-06-14 12:23 泡菜

          好好學習,一定能成功的!相信自己,相信一定有發展的。  回復  更多評論   

          導航

          統計

          留言簿(1)

          隨筆分類(12)

          隨筆檔案(19)

          積分與排名

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 安阳县| 丹江口市| 巴彦淖尔市| 福海县| 洪洞县| 台北市| 福泉市| 贡山| 晋宁县| 宜川县| 图木舒克市| 舞阳县| 韩城市| 商南县| 石屏县| 拉萨市| 铜川市| 聂拉木县| 赤城县| 西吉县| 纳雍县| 沈丘县| 武川县| 横山县| 乳山市| 栾城县| 张掖市| 翁源县| 五常市| 昆明市| 乌兰浩特市| 湖口县| 东乡族自治县| 中西区| 富阳市| 云阳县| 南涧| 襄垣县| 广德县| 荆州市| 临武县|