揭開JVM所看到的try/catch/finally

#揭開JVM所看到的try/catch/finally 最近有一位朋友發了一段代碼給我,這個方法很簡單,具體內容大體以下:java

int num = 5000000;//500萬 
long begin = System.currentTimeMillis();
for(int i=0; i<num; i++){
   try{ 
        //do something
      }catch(Exception e){

      }
 }
long end = System.currentTimeMillis();
System.out.println("==============使用時間:" + (end - begin) + " 毫秒");

上面代碼能夠看到是經過執行該循環體所消耗的時間,經過和把try/cache註釋掉進行對比,最後獲得的結果時間比較隨機,執行的耗時和try/cache沒有必然的聯繫,那try/cache究竟會不會影響代碼的執行效率呢?從java語言的源碼上看貌似多執行了一些指令,其實是怎麼樣的呢?下面我分幾個場景來分析一下jvm對try/cache的處理過程。 ##單層的try/catch 下面是一個只有單層的try/catch代碼塊數組

public int test(int a,int b){
        try{
            return a+b;
        }catch (Exception e){
            throw new CustomException();
        }
    }

經過javap -v查看JVM編譯成class字節碼以後是如何處理這個try/catchjvm

public int test(int, int);
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=3
         0: iload_1                          // 將第一個int參數壓入隊列(第一個入參)
         1: iload_2                          // 將第二個int參數壓入隊列(第二個入參)
         2: iadd                             //彈出隊列中第一個和第二個參數執行相加,並把相加結果壓入隊列
         3: ireturn                          //彈出隊列第一個元素,並return。
         4: astore_3                         //此處是try開始的邏輯
         5: new           #3                 // class com/bieber/demo/CustomException
         8: dup                                
         9: invokespecial #4                 // Method com/bieber/demo/CustomException."<init>":()V
        12: athrow                           //將隊列中的第一個元素彈出,並當作異常拋出,到此整個方法體完畢
     Exception table:
         from    to  target type
             0     3     4   Class java/lang/Exception
     LineNumberTable:
        line 13: 0
        line 14: 4
        line 15: 5
     LocalVariableTable:
        Start  Length  Slot  Name   Signature
               5       8     3     e   Ljava/lang/Exception;
               0      13     0  this   Lcom/cainiao/cilogisticservice/ExceptionClass;
               0      13     1     a   I
               0      13     2     b   I
     StackMapTable: number_of_entries = 1
          frame_type = 68 /* same_locals_1_stack_item */
          stack = [ class java/lang/Exception ]

上面是test方法JVM編譯以後的結果,上面的Code塊是整個方法體的內容,而從0-3能夠視爲是方法體的正常邏輯,4-12能夠視爲try/catch塊,從方法體的指令看,正常狀況下執行到3的地方就完畢了,而不會去執行4-12的指令。那是否是就得出結論,try/catch代碼塊在正常邏輯的時候是不會被執行的,因而對於對代碼加上try/catch塊,並不會影響代碼的執行效率,由於根本不會有多餘的指令被執行,只有出現異常的時候纔會多出執行異常的指令。其實本文到這裏基本上能夠結束了,由於獲得了我想要的答案(try/catch代碼塊對代碼性能的影響)。爲了讓整個問題可以更加全面一點,下面對JVM如何處理一個try/catch作更加深刻的調研。性能

上面的JVM編譯的字節碼的時候除了Code代碼塊,還有Exception table代碼塊,從這個代碼塊的內容能夠看到,包含四列(from,to,target,type),其中fromto表示這個try/catch代碼塊是從哪開始到哪結束,能夠看到上面的try/catch代碼塊是從Code代碼塊的0-3,也就是從加載第一個int值到返回結果的代碼塊,target表示這個try/catch代碼塊執行邏輯在哪裏開始,好比上面的表示從Code中的4開始,也就是astore_3指令開始,直到athrow指令被執行的地方,在Exception table中的一行還有type列,表示是這個異常類型,用於在一個try/catch代碼塊出現多個catch內容,用於匹配正確的異常類型。下面我列出這種狀況:this

##一個try對應多個catch 我將上面的代碼調整成了下面結構:code

public int test(int a,int b){
        try{
            return a+b;
        }catch (Exception e){
            a++;
            throw new CustomException();
        }catch (Throwable t){
            b++;
            throw new CustomException();
        }
    }

JVM對上面代碼編譯後的結果:xml

public int test(int, int);
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=3
         0: iload_1       
         1: iload_2       
         2: iadd          
         3: ireturn       
         4: astore_3      
         5: iinc          1, 1
         8: new           #3                  // class com/bieber/demo/CustomException
        11: dup           
        12: invokespecial #4                  // Method com/bieber/demo/CustomException."<init>":()V
        15: athrow        
        16: astore_3      
        17: iinc          2, 1
        20: new           #3                  // class com/cainiao/cilogisticservice/CustomException
        23: dup           
        24: invokespecial #4                  // Method com/cainiao/cilogisticservice/CustomException."<init>":()V
        27: athrow        
      Exception table:
         from    to  target type
             0     3     4   Class java/lang/Exception
             0     3    16   Class java/lang/Throwable
      LineNumberTable:
        line 13: 0
        line 14: 4
        line 15: 5
        line 16: 8
        line 17: 16
        line 18: 17
        line 19: 20
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               5      11     3     e   Ljava/lang/Exception;
              17      11     3     t   Ljava/lang/Throwable;
               0      28     0  this   Lcom/cainiao/cilogisticservice/ExceptionClass;
               0      28     1     a   I
               0      28     2     b   I
      StackMapTable: number_of_entries = 2
           frame_type = 68 /* same_locals_1_stack_item */
          stack = [ class java/lang/Exception ]
           frame_type = 75 /* same_locals_1_stack_item */
          stack = [ class java/lang/Throwable ]

和上面的內容對比一下會發現,在Code中多出了一段astore_3/athrow塊,而且在Exception table中多了一行,想一想經過上面的解釋,對這個多出的一行的目的應該都知道是用來什麼的,因爲我在catch中成了throw以外,還多了一個++的操做,能夠看到在astore_3/athrow塊中多出了iinc指令,因此能夠理解,try/catch在JVM中對應的是一個子代碼塊,在條件知足(出現匹配的catch異常)的時候會被執行。隊列

下面我整理一下當出現異常的(這裏說的是有try/catch的異常)JVM處理流程:ci

一、在try/catch出現異常
二、JVM會去`Exception table`查找匹配的異常類型
三、假設匹配上了,那麼讀取from,to,target,獲取待執行的`try/catch`塊的指令(具體是否拋出,看是否有athrow指令)。

爲了更加了解JVM對try的處理,下面對try/finally再調研一下。get

##try/finally塊的執行處理

調整代碼邏輯,以下:

public int test(int a,int b){
        try{
            return a+b;
        }catch (Exception e){
            a++;
            throw new CustomException();
        }finally {
            b++;
        }
    }

JVM編譯後的指令:

public int test(int, int);
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=5, args_size=3
         0: iload_1       
         1: iload_2       
         2: iadd          
         3: istore_3                         //將棧頂的元素存儲局部變量數組的第三個位置
         4: iinc          2, 1               //執行b++
         7: iload_3                          //把局部變量第三個位置的數值壓入棧頂
         8: ireturn                          //彈出棧頂,而且返回
         9: astore_3      
        10: iinc          1, 1               //a++
        13: new           #3                  // class com/bieber/demo/CustomException
        16: dup           
        17: invokespecial #4                  // Method com/bieber/demo/CustomException."<init>":()V
        20: athrow        
        21: astore        4
        23: iinc          2, 1                //b++
        26: aload         4
        28: athrow        
      Exception table:
         from    to  target type
             0     4     9   Class java/lang/Exception
             0     4    21   any
             9    23    21   any
      LineNumberTable:
        line 13: 0
        line 18: 4
        line 14: 9
        line 15: 10
        line 16: 13
        line 18: 21
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
              10      11     3     e   Ljava/lang/Exception;
               0      29     0  this   Lcom/cainiao/cilogisticservice/ExceptionClass;
               0      29     1     a   I
               0      29     2     b   I
      StackMapTable: number_of_entries = 2
           frame_type = 73 /* same_locals_1_stack_item */
          stack = [ class java/lang/Exception ]
           frame_type = 75 /* same_locals_1_stack_item */
          stack = [ class java/lang/Throwable ]

經過上面的代碼,你會發如今Exception table都出了兩行,其實咱們只是在代碼中只有一個try/catch塊,而這裏出現了三個,那麼另外兩個是作什麼的呢?能夠看到多出的兩行的type都是any,這裏的any表示的是任何異常類型,多出的第一行,是從0-4,表示0-4之間的指令出現異常,會從21的指令開始執行,發現執行的是b++(finally)的內容,多出的第二行是9-23,表示9-23之間的指令被執行的過程當中出現異常也會從21行開始執行(也是執行finally的內容),而9-23實際上是catch的代碼邏輯。上面均是出現了異常會觸發finally的代碼執行,正常狀況下會發現4的位置執行了finally的內容,而後再執行ireturn指令,這裏能夠得出,JVM處理finally實際上是對於正常的指令隊列增長了finally代碼塊的指令,以及對異常中添加了finally代碼塊的指令,這也就致使了fianlly在任何地方均可以被執行,其實就是冗餘了指令隊列(其實思想比較簡單)。

到此,對JVM如何處理try/catch/finally塊進行了簡單的介紹,目的是讓你們對添加try代碼塊不要吝嗇,在須要的時候,仍是須要作一些異常的控制,讓代碼的異常邏輯更加完善,而不是一直將異常拋給外面處理,由於外面可能並不知道你這個異常是什麼意思。

相關文章
相關標籤/搜索