雖然這次的主題是異常,但是說的更確切點應該叫程序是如何運行的。
編程語言是在計算機的內存中運行起來的,然而運行的時候,無論是基於jvm的java、scala之類的語言,還是像C/C++這樣直接跟操作系統打交道的語言,都會將得到的內存分爲幾個區域,每個區域負責不同的事情。
大體上程序內存被分爲這樣的幾個區域:
堆(heap):malloc、new 這樣的關鍵字生成的對象都會利用這個區間的內存;當然jvm中堆會被進一步的細分爲老年代、新生代,新生代又有伊甸園、倖存者,這個以後有時間專門介紹;
棧(stack):這塊主要是給線程使用的,當線程調用一個方法的時候,就會有一個棧幀(frame)入棧,正常情況下方法執行結束,對應的frame出棧,再調用的新的frame入棧;
方法區:差不多就是程序員寫的代碼被編譯成機器碼之後就會存放在這裏了,hotspot在java8中已經棄用方法區改爲metastore,主要的區別好像就是gc變得容易了。
當然還有其他很多區域,這裏就不一一介紹了,以上幾個區域的關係大概如下圖
下面迴歸正題介紹異常:
異常的大概形式大家應該都很熟了,try、catch、finally在加上不太常用的throw、throws,這裏就不介紹了。大概差不多是像下面這麼使用的:
來分析一下發生異常的時候執行method2的線程的棧的使用情況:
1.首先method2對應的棧幀(frame2)入棧;
2.執行method1即method1對應的棧幀(frame1)入棧;
3.如果method1成功執行,則frame1出棧,接着method2執行成功,method2對應的frame2出棧,線程結束;如果method1拋出異常,首先構建異常對象e,frame1出棧,接着找當前上下文有沒有對應的處理異常的語句(即執行method1的時候有沒有對應的exception的catch語句),發現沒有,所以到上一層即method2的上下文中查找,發現剛好有;
4.catch對應的代碼塊其實也可以看成一個參數是異常e的引用的方法了(就像public void catch(Execption e)),對應的catch frame入棧,執行e.printStackTrace()。
根據上面的分析,可以看出,如果不會發生異常,即使寫了try、catch對程序的效率應該也沒有影響;發生異常最大的影響在於要構造對應的異常對象,並且要不斷地向上迴歸找取處理異常的catch方法。
現在假設有這麼一個場景,需要做兩個整數的減法,假設我們現在還是小學生不知道什麼叫做負數,那麼當被減數小於減數的時候,我們就不知道該怎麼處理了,這就是一種異常了,所以定義如下異常。
接着定義了兩個處理減法操作的方法,一個用作對比不拋出異常(method1),一個是我們觀察的重點要拋出異常(method2):
在以下四種場景下調用1000次減法操作:
1.調用1000次的method1,這個時候根本沒有異常;
2.在保證被減數更大的情況下調用1000次method2,這個時候不會拋出異常;
3.在保證被減數更小的情況下調用1000次method2,這個時候一定拋出異常;
4.在保證被減數更小的情況下調用1000次method2,而調用method2的函數不直接處理異常(沒有對應的catch塊),這個時候一定拋出異常而且按道理會比第三種情況要慢;
以下是十次試驗之後的結果對比:
雖然實驗次數不太多,但是結果還是比較明顯的,在沒有真正發生異常的時候,程序效率不會明顯下降;但是如果真的發生了異常對異常的處理帶來了額外的開銷,而且捕捉的越不及時開銷越大。
相關代碼已經打包上傳,歡迎大家指處不足。
2017.2.5
明天要上班 /(ㄒoㄒ)/~~