深入理解Java之java虛擬機干凈利落的規(guī)范總結(jié) 下
- 作者:新網(wǎng)
- 來源:新網(wǎng)
- 瀏覽:100
- 2018-05-03 17:56:15
要去正確地實現(xiàn)一臺Java虛擬機,就需要正確地讀取class文件中每一條字節(jié)碼指令并且能正確執(zhí)行這些指令所蘊含的操作即可。
<
div> 要去正確地實現(xiàn)一臺Java
虛擬機,就需要正確地讀取class文件中每一條字節(jié)碼指令并且能正確執(zhí)行這些指令所蘊含的操作即可。
由Java虛擬機執(zhí)行的每個方法都會配有零到多個異常處理器。異常處理器描述了其在方法代碼中的有效作用范圍(通過字節(jié)碼偏移量范圍來描述)、能處理的異常類型以及處理異常的代碼所在的位置。要判斷某個異常處理器是否可以處理某個具體的異常,需要同時檢查一場出現(xiàn)的位置是否在異常處理的有效作用范圍內(nèi),以及出現(xiàn)的異常是否是異常處理器聲明可以處理的異常類型或其子類型。當(dāng)拋出異常時,Java虛擬機搜索當(dāng)前方法包含的各個異常處理器,如果能找到可以處理該異常的異常處理器,則將代碼控制權(quán)轉(zhuǎn)向異常處理器中描述的處理異常的分支之中。
首先簡單介紹一下main方法中各條字節(jié)碼指令所代表的意思:
0 : 將int類型的常量i壓入操作數(shù)棧中,iconst_0后面跟的那個0代表常量的值為0;
1:將一個int類型數(shù)據(jù)由保存到本地變量表,istort_1后面的1代表的是指向當(dāng)前棧幀中局部變量表的索引值;
2:將一個值為3的int類型的常量壓入操作數(shù)棧中。在這里指的是被除數(shù)3;
3:從局部變量表加載一個int類型值到操作數(shù)棧中,這里指的是除數(shù)i;
4:對兩個int類型的數(shù)據(jù)做除法;
5:將兩數(shù)相除之后所得到的int類型數(shù)據(jù)保存到本地變量表;
6:假如沒有發(fā)生異常的話,那么執(zhí)行完goto到第14條語句,函數(shù)正常返回;
9:假如發(fā)生了除零異常,就執(zhí)行這條指令,將異常對象保存到局部變量表中;
10:從局部變量表中加載剛才的那個異常對象到操作數(shù)中;
11:調(diào)用異常對象的printStackTrace方法
14:不管是正常完成還是異常完成,最終都會返回。
在字節(jié)碼下方可以看到一個Exception table。那么它是什么東西呢?其實我們很容易能夠理解它就是異常表,也就是前面我們提過的異常處理器。我們可以明顯地觀察出,其實try-catch代碼塊編譯之后似乎沒有生成任何指令。那么Java語言中的try-catch放到字節(jié)碼當(dāng)中對應(yīng)什么東西呢?其實就是對應(yīng)這個異常處理器。下面我們來解讀一下異常處理器:
在try語句塊的執(zhí)行過程中如果沒有拋出異常,那么這個異常處理器不會起作用。異常處理器的作用范圍是從字節(jié)碼的第2行到第6行,也就是from-to標(biāo)明的范圍。假如編譯好的代碼里面第2~6句之間有一個類型為
java.lang.ArithmeticException的異常實例被拋出,那么操作將轉(zhuǎn)移至第9句繼續(xù)執(zhí)行,即進入catch語句塊的實踐步驟。假如說拋出的異常不是ArithmeticException實例,那么異常處理器就不能處理該異常,這個異常將返回給上一級的調(diào)用者。
那假如try語句塊包含多個catch語句塊,在編譯好的代碼中會出現(xiàn)什么樣的結(jié)果呢?
如果給定的try語句塊包含多個catch語句塊,那么在編譯好的代碼中,多個catch語句塊的內(nèi)容將會連續(xù)排列,在異常表中也會有對應(yīng)的連續(xù)排列的成員,它們的排列順序和源碼中catch語句塊的出現(xiàn)順序一致。main方法在執(zhí)行時,如果try語句塊中拋出了一個異常,這個異常將會被多個catch語句塊捕獲。假如第一個catch不能捕獲異常(當(dāng)然這里的第一個catch語句塊肯定是能處理ArithmeticException,我只是舉個例子),那么異常將交由第二個異常處理器來進行處理,這很容易理解。因為我在第二個catch語句塊中選擇的是將捕獲的異常拋出,所以在字節(jié)碼的第26行可以看到有一個athrow指令,在前面的學(xué)習(xí)當(dāng)中我們知道它是拋出異常的意思,其實也就是對應(yīng)著Java代碼中的throw new Exception()。在這里,我還要順便介紹一下Java創(chuàng)建一個對象的代碼在編譯之后會產(chǎn)生怎樣的字節(jié)碼。
其實,剛才我所說的throw new Exception()對應(yīng)athrow字節(jié)碼指令只說對了一半,它在編譯之后不僅僅只產(chǎn)生athrow這一條字節(jié)碼指令。因為它還對應(yīng)著一個操作,也就是new一個Exception對象。Java語言實例化一個Exception對象將會產(chǎn)生三條字節(jié)碼指令,即上圖中19,22,23三行:
為什么會有三條指令呢?dup是做什么的?我們下面一起來學(xué)習(xí)一下
由于討論的是創(chuàng)建對象,所以在代碼throw new Exception()中我們不看throw,只看new Exception()這一部分代碼。
new Exception()表達式的作用是:
創(chuàng)建并默認初始化一個Exception對象;
調(diào)用Exceptioon類的signature為()V的構(gòu)造器;
表達式的值為一個指向這個對象的引用
對應(yīng)字節(jié)碼,我們可以看到:
new Exception()對應(yīng)上面的1
invokespecial Exception.()V對應(yīng)上面的2
那么3是怎么來的?
回歸到字節(jié)碼,我們可以看到new字節(jié)碼指令的作用是創(chuàng)建指定類型的對象實例、對其進行默認初始化,并將指向該實例的一個引用壓入操作數(shù)棧頂;
然后因為invokespecial會消耗操作數(shù)棧頂?shù)囊米鳛閭鹘o構(gòu)造器的"this"參數(shù),所以如果我們希望在invokespecial調(diào)用后在操作數(shù)棧頂還維持有一個指向新建對象的引用,就得在invokespecial之前先“復(fù)制”一份引用----這就是dup的來源。
以上,就是對創(chuàng)建一個對象編譯之后產(chǎn)生的字節(jié)碼的解釋
編譯finally語句塊
剛才我們介紹了異常處理在字節(jié)碼層面的細節(jié),但是我們還需要注意的是----由于finally能夠保證不管發(fā)生任何情況,都能夠執(zhí)行語句塊中的代碼,所以在日常編碼過程中我們在可能發(fā)生異常的地方(或者是不會發(fā)生異常的地方)經(jīng)常使用finally來釋放某些資源。
下面我們從虛擬機層面來看看如何保證finally語句塊中的代碼一定會執(zhí)行
可以看到,其實編譯器是通過在每個分支后面增加冗余代碼的形式來保證finally語句塊中的代碼一定會被執(zhí)行。這里和書上講的有點出入,書上在講解這一塊的時候還是用jsr、jsr_w、ret等程序控制轉(zhuǎn)移指令來解釋的,但是javac在很早之前就不再為finally語句生成jsr和ret指令了。
如果程序在try語句塊中執(zhí)行了return,那么代碼的行為如下:
如果有返回值,將返回值保存在局部變量表;
執(zhí)行跟在后面的冗余finally語句塊中的代碼;
在finally執(zhí)行完之后,將事先保存在局部變量表中的返回值壓入操作數(shù)棧中之后返回。
如果在try語句中拋出異常,那么代碼的行為如下:
將異常保存在局部變量表中
執(zhí)行finally語句塊中的代碼
在執(zhí)行完finally語句塊中的代碼后,重新拋出這個事先保存好的異常。
Java虛擬機中的同步(synchronization)使用monitor的進入和退出來實現(xiàn)的。無論顯式同步(有明確的monitorenter和monitorexit指令),還是隱式同步(依賴方法調(diào)用和返回指令實現(xiàn))都是如此。
在Java語言中,同步用得最多的地方可能是經(jīng)synchronized所修飾的同步方法。同步方法并不是用monitorenter和monitorexit來實現(xiàn)的,而是由方法調(diào)用指令讀取運行時常量池中方法的ACC_SYNCHRONIZED標(biāo)志來隱式實現(xiàn)的。
monitorenter和monitorexit指令用于編譯同步語句塊
編譯器必須確保無論方法以何種方式完成(正常結(jié)束或者是異常結(jié)束),方法中調(diào)用過的每條monitorenter指令都必須有對應(yīng)的monitorexit指令得到執(zhí)行。為了確保在方法異常完成時,monitorenter和monitorexit指令依然可以正確配對執(zhí)行,編譯器會自動生成一個異常處理器,這個異常處理器宣稱自己可以處理所有異常,它的代碼用來執(zhí)行monitorexit指令。
到這里深入理解Java之java虛擬機干凈利落的規(guī)范總結(jié)就結(jié)束了,不足之處還望大家多多包涵!!覺得收獲的話可以點個關(guān)注收藏轉(zhuǎn)發(fā)一波喔,謝謝大佬們支持。