我對(duì)Java的異常處理機(jī)制的理解
?? 首先從一個(gè)問題引出一個(gè)角度:跑java程序的機(jī)器在如何運(yùn)行?
?? Jvm 和物理機(jī)器構(gòu)成了一個(gè)運(yùn)行環(huán)境。這個(gè)運(yùn)行環(huán)境“吸收”提供的class文件或其他資源,包括java main入口的給定參數(shù)等等,以多線程thread形式運(yùn)行著。線程在另一個(gè)線程中誕生出來,某一天魂歸西天。在線程誕生之初就屬于一個(gè)分組threadgroup。
?? 然后描述另外一個(gè)角度:所謂運(yùn)行環(huán)境吸收的材料中,比如class文件,其實(shí)是我們程序員給的運(yùn)行規(guī)定。就是說我們按java language specification給出了我們對(duì)如何運(yùn)行的命令信息。實(shí)際運(yùn)行和我們的意愿之間到底是怎么相關(guān)呢?
運(yùn)行環(huán)境的出現(xiàn)之初就會(huì)按我們的意愿試圖執(zhí)行某個(gè)類的main.首先運(yùn)行環(huán)境會(huì)誕生一個(gè)主線程,這個(gè)主線程首先要求運(yùn)行環(huán)境已經(jīng)initializing這個(gè)類,沒有就得initializing這個(gè)類,在做initializing之前又得要求linking,以致loading,不管怎么樣,這樣的約定導(dǎo)致主線程會(huì)運(yùn)行loading,linking,initializing,然后再運(yùn)行main方法里我們表達(dá)的意思。當(dāng)然在這個(gè)主線程里也會(huì)按我們的意思誕生新線程,這個(gè)新線程也還是得在誕生之初按我們的意愿試圖執(zhí)行那個(gè)runable類的run方法,這當(dāng)然也得要求initializing。所不同的是有可能這個(gè)initializing在某個(gè)其他線程里給運(yùn)行了,那么也就是說運(yùn)行時(shí)環(huán)境滿足了這個(gè)要求,那在當(dāng)前線程中就不干這個(gè)事了。
?? 最后我們來看異常的概念。
?? 顯然我們的意愿是不容違背的。呵呵??墒乾F(xiàn)實(shí)歸現(xiàn)實(shí),總有那么多不盡人意。當(dāng)運(yùn)行時(shí)環(huán)境參照我們的意愿以各個(gè)線程運(yùn)行時(shí),總可能出現(xiàn)點(diǎn)違背意愿的事,也就是異常。這個(gè)情況的出現(xiàn)可能是機(jī)器內(nèi)存條抖了下,可能是我們的意愿就不正常,比如想數(shù)組越界訪問,或者我們的意愿就是“請(qǐng)此刻馬上當(dāng)作異常發(fā)生了”等等。那么這時(shí)會(huì)怎么運(yùn)行呢?
?? 這種情況當(dāng)然是在線程中發(fā)生的,發(fā)生后運(yùn)行時(shí)大師就會(huì)根據(jù)當(dāng)前情況構(gòu)造一throwable實(shí)例,然后尋找本線程要轉(zhuǎn)到哪個(gè)點(diǎn)上去繼續(xù)運(yùn)行。這到底是哪個(gè)點(diǎn),這個(gè)意愿當(dāng)然還得我們程序員通過java language specification給出命令信息。我們的意愿不一定非要寫出來,有一個(gè)是約定好的:異常傳播跳出前釋放同步塊的鎖。那么如果運(yùn)行時(shí)大師找到了這個(gè)點(diǎn),就會(huì)安排本線程繼續(xù)從這個(gè)點(diǎn)運(yùn)行,找不到,那么運(yùn)行時(shí)大師就會(huì)把該線程殺了。殺之前先執(zhí)行它的threadgroup的uncaughtException方法里我們表達(dá)的意愿。注意不管是在那個(gè)點(diǎn)還是這個(gè)方法,我們都有機(jī)會(huì)表達(dá)對(duì)那個(gè)throwable實(shí)例怎么怎么樣的想法。
?? Ok, 現(xiàn)在基本框架已經(jīng)有了,再具體的描述下。
?? 剛才說到了每個(gè)線程的運(yùn)行,異常是有很多種情況的。那這種情況算什么時(shí)候發(fā)生的呢?總起來說就是什么時(shí)候觸發(fā)了執(zhí)行,什么時(shí)候算。比如我們有一句a=1/b.當(dāng)線程參照這句執(zhí)行時(shí),發(fā)現(xiàn)b=0,那就可以說在這時(shí)發(fā)生了個(gè)異常。再比如我們某StringUtils.contact(s1,s2);當(dāng)線程參照這句執(zhí)行時(shí),可能就會(huì)要求先做StringUtils的resolving(linking的最后一個(gè)可選步驟),但也可能在線程link該句所屬的類時(shí)就會(huì)做,這根據(jù)jvm規(guī)范得看jvm怎么實(shí)現(xiàn)了。但不管怎么說,什么時(shí)候做的resolving,而在這個(gè)過程中出異常了,就得算這個(gè)點(diǎn)。這樣就有一個(gè)意識(shí):發(fā)生點(diǎn)是可以嵌套的。比如a方法a步調(diào)用b方法,但b里b步拋異常了,我們可以說b步是個(gè)點(diǎn),但對(duì)a來說,a步也是這個(gè)點(diǎn)。最原始的那個(gè)點(diǎn)怎么算?怎么說都無所謂。但有謂的是下面:
?? 運(yùn)行時(shí)大師就會(huì)根據(jù)當(dāng)前情況構(gòu)造一個(gè)throwable實(shí)例!其中當(dāng)前情況就有一個(gè)當(dāng)前線程的執(zhí)行棧信息。這個(gè)當(dāng)前信息怎么算有所謂。而且,然后尋找線程要轉(zhuǎn)到哪個(gè)點(diǎn)上去繼續(xù)運(yùn)行。從哪里開始找起也有所謂。這兩個(gè)所謂都在jvm中規(guī)范了。
?? 關(guān)鍵是我們程序員如何表達(dá)。
-
?? 1 當(dāng)前信息就是線程當(dāng)時(shí)的棧信息。注意這個(gè)棧信息是我們的程序(包括Lib)的某個(gè)方法名的一些信息,而不是細(xì)化到某個(gè)操作2進(jìn)制指令。
-
?? 2 從哪里找起?
?? 我們的程序中哪句是最終導(dǎo)致異常的,從這一句所在的代碼塊開始找起。所以主線程一開始時(shí)initialize那個(gè)main類,如果jvm實(shí)現(xiàn)是最早link,那么可能會(huì)由此遞歸initialize很多main類應(yīng)用的類,如果這個(gè)過程中出異常了,我們甚至就沒有必要找了,之后怎么處理無所謂;如果initialize后執(zhí)行main方法過程中某句導(dǎo)致出異常了,那就得從這一句所在的代碼塊開始找起。Try{ ~~~;a;~~~;}catch{}finally{} 。Catch不匹配就要跳出這個(gè)try塊,但跳出之前也要先讓本線程運(yùn)行完finally。如果catch,finally又拋異常了,那就形成一個(gè)新異常,原來的異常的信息不見了,所以要小心。注意“那個(gè)最終導(dǎo)致異常的那一句”是這個(gè)意思,比如Try{~~~;a();~~~;}catch{}finally{}。a()出異常了,但不是a()句,而是a()方法中出異常的最終那一句,由此遞歸到程序可見的最終一句。從那個(gè)地方找起。
異常多數(shù)是同步發(fā)生的,就是在線程的某個(gè)固定環(huán)節(jié)發(fā)生,但也有異步的,比如內(nèi)存溢出,誰也不知道會(huì)在哪個(gè)線程中哪個(gè)點(diǎn)發(fā)生,對(duì)于這樣的異步異常,jvm規(guī)范中說jvm實(shí)現(xiàn)讓這種異常實(shí)際發(fā)生后代碼繼續(xù)運(yùn)行一有限段,所以給出一個(gè)簡單實(shí)現(xiàn)模型:運(yùn)行時(shí)對(duì)這種異常的檢測(cè)時(shí)每次線程做控制轉(zhuǎn)移指令時(shí),比如條件轉(zhuǎn)移啊等等。
?? 但是無論什么異常,對(duì)于我們用java表達(dá)的意愿,必須滿足:異常邏輯發(fā)生時(shí)之前的java都執(zhí)行了,之后的都沒有執(zhí)行。至于實(shí)際實(shí)現(xiàn)中,可能我們看到的異常發(fā)生時(shí)刻和實(shí)際的發(fā)生時(shí)刻有差別(因?yàn)檫\(yùn)行時(shí)檢測(cè)不是實(shí)時(shí)的),也可能我們看到的代碼執(zhí)行和編譯器優(yōu)化后實(shí)際的執(zhí)行順序不一致,但不管怎么樣,我們看到的和表達(dá)的必須一致。
?? 最后看異常的分類。我截至目前說的異常都是一個(gè)廣義的概念,實(shí)際上應(yīng)該對(duì)應(yīng)throwable這個(gè)類。Throwable之下有exception和error.exception下有很多,其中有一個(gè)是RuntimeException。我們用他們表達(dá)對(duì)運(yùn)行的意愿,同時(shí)我們也用他們表達(dá)我們對(duì)提供的接口運(yùn)行時(shí)可能出現(xiàn)的異常的提示。這種表達(dá)是java的特色,一般語言不會(huì)直接表達(dá)這種可能性。假如說我們的某個(gè)方法可能拋exception,意思是別人使用這個(gè)方法時(shí)能處理這個(gè)異常,對(duì)非runtimeexception一定要么捕捉下,要么繼續(xù)傳播;對(duì)runtimeexception,可以不做處理。而假如說我們的某個(gè)方法可能拋error,意思則是別人用這個(gè)方法可能會(huì)面對(duì)error,而且不認(rèn)為別人能處理。從另一個(gè)方面,對(duì)于我們使用某個(gè)方法,如果他聲明會(huì)拋一個(gè)非runtimeexception,我們必須處理,真有error的可能我們都不用,當(dāng)然也可能記下日志就算了。考慮到這種含義,編譯器幫助我們一定要處理非runtimeexception。