#揭開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/catch
的jvm
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),其中from
和to
表示這個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
代碼塊不要吝嗇,在須要的時候,仍是須要作一些異常的控制,讓代碼的異常邏輯更加完善,而不是一直將異常拋給外面處理,由於外面可能並不知道你這個異常是什麼意思。