<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    上善若水
    In general the OO style is to use a lot of little objects with a lot of little methods that give us a lot of plug points for overriding and variation. To do is to be -Nietzsche, To bei is to do -Kant, Do be do be do -Sinatra
    posts - 146,comments - 147,trackbacks - 0

    問題描述

    先來看一下以下的代碼,猜猜他們會是什么樣的結果:

      1 public class FinallyIssue {
      2     public static void main(String[] args) {
      3        System.out.println("finallyReturnTest : ");
      4        System.out.println("return value : " + finallyReturnTest(1));
      5        System.out.println("return value : " + finallyReturnTest(-1));
      6       
      7        System.out.println("finallyBreakTest : ");
      8        System.out.println("return value : " + finallyBreakTest(true));
      9        System.out.println("return value : " + finallyBreakTest(false));
     10       
     11        System.out.println("valueChangeInFinallyTest : ");
     12        System.out.println("return value : " + valueChangeInFinallyTest());
     13       
     14        System.out.println("valueChangeReturnInFinallyTest : ");
     15        System.out.println("return value : " + valueChangeReturnInFinallyTest());
     16       
     17        System.out.println("refValueChangeInFinallyTest : ");
     18        System.out.println("return name : " + refValueChangeInFinallyTest().name);
     19     }
     20    
     21     private static boolean finallyReturnTest(int value) {
     22        try {
     23            if(value > 0) {
     24               return true;
     25            } else {
     26               return false;
     27            }
     28        } finally {
     29            return false;
     30        }
     31     }
     32    
     33     private static boolean finallyBreakTest(boolean value) {
     34        while(value) {
     35            try {
     36               return true;
     37            } finally {
     38               break;
     39            }
     40        }
     41        return false;
     42     }
     43    
     44     private static int valueChangeInFinallyTest() {
     45        int i = 10;
     46        int j = 1;
     47        try {
     48            i = 100;
     49            j = 2;
     50            System.out.println("try : i = " + i);
     51            System.out.println("try : j = " + j);
     52            return i;
     53        } catch(Exception e) {
     54            e.printStackTrace();
     55        } finally {
     56            i = 1000;
     57            j = 3;
     58            System.out.println("finally : i = " + i);
     59            System.out.println("finally : j = " + j);
     60        }
     61       
     62        return i;
     63     }
     64    
     65     private static int valueChangeReturnInFinallyTest() {
     66        int i = 10;
     67        int j = 1;
     68        try {
     69            i = 100;
     70            j = 2;
     71            System.out.println("try : i = " + i);
     72            System.out.println("try : j = " + j);
     73            return i;
     74        } catch(Exception e) {
     75            e.printStackTrace();
     76        } finally {
     77            i = 1000;
     78            j = 3;
     79            System.out.println("finally : i = " + i);
     80            System.out.println("finally : j = " + j);
     81            return i;
     82        }
     83     }
     84    
     85     private static Person refValueChangeInFinallyTest() {
     86        Person p = new Person();
     87        try {
     88            p.name = "person1";
     89            System.out.println("try : Person name is : " + p.name);
     90            return p;
     91        } catch(Exception e) {
     92            e.printStackTrace();
     93        } finally {
     94            p.name = "person2";
     95            System.out.println("finally : Person name is : " + p.name);
     96        }
     97       
     98        p.name = "person3";
     99        System.out.println("out : Person name is : " + p.name);
    100       
    101        return p;
    102     }
    103    
    104     static class Person {
    105        public String name;
    106     }
    107 }

    這樣一段代碼的結果會是什么呢?

    以下是運行結果:

    finallyReturnTest :

    return value : false

    return value : false

    finallyBreakTest :

    return value : false

    return value : false

    valueChangeInFinallyTest :

    try : i = 100

    try : j = 2

    finally : i = 1000

    finally : j = 3

    return value : 100

    valueChangeReturnInFinallyTest :

    try : i = 100

    try : j = 2

    finally : i = 1000

    finally : j = 3

    return value : 1000

    refValueChangeInFinallyTest :

    try : Person name is : person1

    finally : Person name is : person2

    return name : person2

     

    這個結果很出乎我的意料,我們知道finally總是會在try-catch語句塊執行完后執行,不管try語句塊中是否已經返回或者拋出了異常。

     

    但是在上面的代碼測試中,如果finally語句塊中有returnbreakcontinue等語句,那么它們會覆蓋try語句塊中的returnbreakcontinue的語句,如以上的finallyReturnTest()finallyBreakTest()valueChangeReturnInFinallyTest()三個函數。

    另外,如果在finally語句塊中修改要返回的值類型變量的值,則這些修改不會保存下來,如valueChangeInFinallyTest()函數;如果要返回的值是引用類型,則修改引用類型的內部成員的值會保存下來。

    如何解釋這個結果呢?

     

    問題解釋

    結合《深入Java虛擬機(第二版)》這本書和代碼編譯后產生的二進制指令代碼,我對以上問題做了部分解釋,鑒于我的才疏學淺,有些觀點是有誤的,希望高手指正(有誤的觀點容易引起誤導,這也是所以我一直非常小心,奈何水平有限,有些時候難免出錯)。

     

    在《深入Java虛擬機(第二版)》的第18章中提到,在早期的Java中,finally的行為是通過JSR指令來實現的,并且為這個指令引入了微型子程序的概念。我的理解,所謂微型子程序就是在函數A中嵌入一個不完整的函數B的調用。比如在這本書上的一個例子:

        private static int microSubroutine(boolean bValue) {
           
    try {
               
    if(bValue) {
                  
    return 1;
               }
               
    return 0;
           } 
    finally {
               System.out.println(
    "finally");
           }
        }

    會生成以下的二進制代碼:

     0 iload_0

     1 ifeq 11

     4 iconst_1

     5 istore_1

     6 jsr 24

     9 iload_1

    10 ireturn

    11 iconst_0

    12 istore_1

    13 jsr 24

    16 iload_1

    17 ireturn

    18 astore_2

    19 jsr 24

    22 aload_2

    23 athrow

     

    24 astore_3

    25 getstatic #7 <Field java.io.PrintStream out>

    28 ldc #1 <String “finally”>

    30 invokevirtual #8 <Method void println(java.lang.String)>

    33 ret 3

     

    如上,24前綴的代碼行以后的部分就是微型子程序,在每一個出口之前都會用JSR調用這個微型子例程序,在這個微型子例程序返回(ret)后,返回調用JSR指令的下一條指令,然后返回(ireturnathrow)。

    jsr指令和ret指令的格式如下:

    jsr    branchbyte1, branchbyte2

    把返回地址壓棧,跳轉至((branchbyte1<<8) | branchbyte2)的位置繼續之行。

    ret index

    返回在index指示的局部變量中存儲的值(位置)。

     

    在上面的二進制代碼中,每次通過jsr 24跳轉到微型子程序,它先將返回地址(jsr 24指令的下一條指令的地址)保存在index3的局部變量中,執行完微型子程序后,通過ret 3返回到調用jsr 24指令的下一條指令執行,并最終執行返回。

     

    可是后來(有人說是自1.4.2后),JVM中取消了jsr指令了,所有finally內部的代碼都內聯到源代碼中了(二進制的源代碼)。所以以上的代碼在之后的編譯器中會產生如下的二進制代碼:

         0 iload_0 [bValue]

         1 ifeq 14

         4 getstatic java.lang.System.out : java.io.PrintStream [16]

         7 ldc <String "finally"> [94]

         9 invokevirtual java.io.PrintStream.println(java.lang.String) : void [24]

        12 iconst_1

    13 ireturn

     

        14 getstatic java.lang.System.out : java.io.PrintStream [16]

        17 ldc <String "finally"> [94]

        19 invokevirtual java.io.PrintStream.println(java.lang.String) : void [24]

        22 iconst_0

    23 ireturn

     

        24 astore_1

        25 getstatic java.lang.System.out : java.io.PrintStream [16]

        28 ldc <String "finally"> [94]

        30 invokevirtual java.io.PrintStream.println(java.lang.String) : void [24]

        33 aload_1

        34 athrow

     

    額,貌似有點偏題了,以上的描述是為了解釋《深入Java虛擬機(第二版)》中對finally描述過時的描述。下面讓我們來真正的解決這個問題。還是從生成的Java二進制代碼入手。

     

    首先來看一下valueChangeInFinallyTest()函數的二進制代碼(注釋了打印語句,使代碼簡潔):

             //int i = 10

         0 bipush 10

         2 istore_0 [i]

           //int j = 1

         3 iconst_1

         4 istore_1 [j]

           //i = 100

         5 bipush 100

         7 istore_0 [i]

           //j = 2

         8 iconst_2

         9 istore_1 [j]

           //保存i的值,因為它是要返回的

        10 iload_0 [i]

    11 istore 4

    //--------------------------------內聯finally語句塊(開始)----------------------

    //i = 1000

        13 sipush 1000

    16 istore_0 [i]

    //j = 3

        17 iconst_3

    18 istore_1 [j]

    //--------------------------------內聯finally語句塊(結束)----------------------

    //加載保存后的i的值,并返回。這里返回的是finally語句塊執行前的i(由istore 4語句緩存起來)的值,因而在finally語句塊中任何對i的操作并不會保留下來。這是在沒有異常發生的情況下。

        19 iload 4

    21 ireturn

     

        22 astore_2 [e]

        23 aload_2 [e]

    24 invokevirtual java.lang.Exception.printStackTrace() : void [104]

    //--------------------------------內聯finally語句塊(開始)----------------------

        27 sipush 1000

        30 istore_0 [i]

        31 iconst_3

    32 istore_1 [j]

    //--------------------------------內聯finally語句塊(結束)----------------------

    33 goto 45

     

    36 astore_3

    //--------------------------------內聯finally語句塊(開始)----------------------

        37 sipush 1000

        40 istore_0 [i]

        41 iconst_3

    42 istore_1 [j]

    //--------------------------------內聯finally語句塊(結束)----------------------

    //而在異常發生但沒有被正確處理的情況下,返回值已經沒有什么意義了。

        43 aload_3

    44 athrow

     

    //這里是在有異常發生,并且異常得到了正確處理的情況下返回的,此時在finally語句塊中對i的操作就會保存下來,并返回給調用者。

        45 iload_0 [i]

        46 ireturn

    相信以上的注釋已經能很好的的解決這個問題了(注:這里j的存在是為了證明在內聯finally語句塊的時候,它只緩存返回值i,而無須緩存其他變量的值,如j的值)。需要特別注意的一點是,如果正常返回的話,finally語句塊中修改i的值是保存不下來的,但是如果出現異常,并被正常捕獲后,在finally語句塊中修改的i的值就會保存下來了。

     

    那么對valueChangeReturnInFinallyTest()函數中的現象如何解釋呢?對這個問題解釋,首先要理解ireturn的指令。ireturn指令沒有操作數,它把當前操作棧的棧頂的int值作為默認的操作數。ireturn指令會彈出當前棧頂的int值,將其壓入調用者的操作棧中,同時忽略當前操作棧中的其他值,即函數正常返回。因而如果在不優化的情況下,在finally語句塊中的return語句會返回當前棧頂的int值(修改后的i值),然后函數返回,此時棧上的其他操作數就被忽略了,并且原本應該執行的ireturn語句也不會之行了。這種方式甚至會忽略拋出的異常,即使當前方法有異常拋出,它的調用方法還是認為它正常返回了。

    如果查看優化后的valueChangeReturnInFinallyTest()方法的二進制源碼后,會發現當前的代碼更加簡潔了。但是它還是沒有避免在finally語句塊中使用return后,會忽略沒有捕獲到的異常的問題。

             //int i = 10

         0 bipush 10

         2 istore_0 [i]

           //int j = 1

         3 iconst_1

         4 istore_1 [j]

           //i = 100

         5 bipush 100

         7 istore_0 [i]

           //j = 2

         8 iconst_2

         9 istore_1 [j]

    10 goto 22

    //catch block

        13 astore_2 [e]

        14 aload_2 [e]

        15 invokevirtual java.lang.Exception.printStackTrace() : void [104]

        18 goto 22

    21 pop

    //--------------------------------內聯finally語句塊(開始)----------------------

    //i = 100

        22 sipush 1000

    25 istore_0 [i]

    //j = 3

        26 iconst_3

    27 istore_1 [j]

    //--------------------------------內聯finally語句塊(結束)----------------------

    //返回finally語句塊中i的值

        28 iload_0 [i]

        29 ireturn

    經過以上的解釋,我想對refValueChangeInFinallyTest()函數中的現象就比較好解釋了,因為當進入finally語句塊的時候,保存的只是Person實例的一個引用,在finally語句塊中依然可以通過引用操作Person內部成員的,因而在finally語句塊中的修改才能保存下來。

     

    而經過編譯器優化后的finallyReturnTest()finallyBreakTest()函數生成的二進制代碼就成一樣的了:

         0 iload_0 [value]

         1 ifeq 8

         4 goto 8

         7 pop

         8 iconst_0

         9 ireturn

     

    后記

    原本以為這是一個小問題的,沒想到花了我一個下午的時間才把問題說清楚了,而在描述問題的過程中,我對問題的本質也看的更加清晰了。這個問題開始是源于我在論壇http://www.javaeye.com/topic/458668中看到,感覺論壇里面的人都沒很好的說清楚這個問題,剛好我看完了《深入Java虛擬機(第二版)》的書,就把這個問題完整的描述出來了。

                                                                                                                                                                             于2010年9月24日

    注:這些文章都是前些時候寫的,之前博客很亂,也都是隨便貼一些自己寫的或轉載的,還有一些則是沒有貼出來過的。現在打算好好整理一下,完整的記錄自己的一些學習歷程,而每次看到過去的時間,則讓我想起以前的日子,因而我對時間一直是很重視的,所以每篇都著名寫的日期,直到最先的文章出現。:)
    posted on 2011-06-24 22:37 DLevin 閱讀(2501) 評論(5)  編輯  收藏

    FeedBack:
    # re: finally知多少
    2011-06-25 22:17 | CodePro
    不錯,很深入  回復  更多評論
      
    # re: finally知多少
    2011-06-28 09:38 | hongfeng-Maple
    還是很不錯的哈  回復  更多評論
      
    # re: finally知多少
    2011-06-28 14:44 | 雷斌輝
    不錯 不錯  回復  更多評論
      
    # re: finally知多少[未登錄]
    2011-10-16 18:18 | chris
    這些天在學習字節碼相關的東東,很感謝樓主的分享,采用JDK1.6編譯了一下樓主的代碼,發現一下詭異的問題:
    //加載保存后的i的值,并返回。這里返回的是finally語句塊執行前的i(由istore 4語句緩存起來)的值,因而在finally語句塊中任何對i的操作并不會保留下來。這是在沒有異常發生的情況下。

    19 iload 4
    21 ireturn

    我的控制臺顯示的iload0, 就是說返回的是經過finally語句賦值后的i, 而并不是在try塊中的i, 在eclipse調試運行得出的也是相同的結果,不知是怎么回事  回復  更多評論
      
    # re: finally知多少[未登錄]
    2011-10-16 18:37 | chris
    抱歉,樓主是對的,我運行的是函數valueChangeReturnInFinallyTest,差別是如果finally里面有return語句的情況下,最終返回的是經過finally修改的值,在此情況下執行的字節碼為:
    iload0
    ireturn
    否則返回的為try塊中的值,執行的字節碼為
    iload4
    ireturn  回復  更多評論
      

    只有注冊用戶登錄后才能發表評論。


    網站導航:
     
    主站蜘蛛池模板: 亚洲免费视频观看| 色综合久久精品亚洲国产| 2020久久精品国产免费| 亚洲av永久无码精品网址| 在线日韩日本国产亚洲| 99热这里只有精品6免费| 亚洲AV无码一区二区大桥未久 | 国产成人高清精品免费软件| 国产精品免费久久久久久久久| 久久精品国产精品亚洲艾草网| 在线看片人成视频免费无遮挡| a毛片成人免费全部播放| 亚洲伊人久久大香线蕉啊| 亚洲成av人片一区二区三区| 2019中文字幕免费电影在线播放| 在线亚洲v日韩v| 亚洲欧洲日本精品| 亚洲男人的天堂www| 免费看美女让人桶尿口| 99久久免费中文字幕精品| 免费看一级毛片在线观看精品视频| 亚洲美女视频一区| 亚洲一区精品伊人久久伊人| 国产在线观看片a免费观看| 日韩精品无码免费专区午夜| 久久久久久亚洲精品无码| 亚洲精品福利在线观看| 国产亚洲AV夜间福利香蕉149 | 亚洲精品自在线拍| 中文字幕亚洲图片| 国产免费av一区二区三区| 国产精品视频免费| 免费国产黄网站在线观看视频 | 亚洲国产精品自在自线观看| 久久久久久亚洲Av无码精品专口 | 国产成人综合久久精品免费| 我的小后妈韩剧在线看免费高清版| 91成人免费福利网站在线| 免费无遮挡无遮羞在线看| 亚洲暴爽av人人爽日日碰| 国产精品亚洲片在线va|