讀書筆記——深入理解Java虛擬機第三版(七章)

第七章 類加載機制

Java虛擬機把描述類的數據從Class文件加載到內存,並對數據進行校驗、轉換解析和初始化,最
終形成可以被虛擬機直接使用的Java類型,這個過程被稱作虛擬機的類加載機制

與那些在編譯時需要進行連接的語言不同,在Java語言裏面,類型的加載、連接和初始化過程都是在程序運行期間完成
,這種策略讓Java語言進行提前編譯會面臨額外的困難,也會讓類加載時稍微增加一些性能開銷,
但是卻爲Java應用提供了極高的擴展性和靈活性,Java天生可以動態擴展的語言特性就是依賴運行期動
態加載和動態連接這個特點實現的。

注:解析階段可能在初始化之後就開始,爲了支持運行時綁定的特性。

 

第一個階段“加載”,《Java虛擬機規範》中並沒有進行強制約束,這點可以交給虛擬機的具體實現來自由把握。但是對於初始化階段,《Java虛擬機規範》則是嚴格規定了有且只有六種情況必須立即對類進行“初始化”(而加載、驗證、準備自然需要在此之
前開始):

主動初始化:

1)遇到new、getstatic、putstatic或invokestatic這四條字節碼指令時,如果類型沒有進行過初始化,則需要先觸發其初始化階段。能夠生成這四條指令的典型Java代碼場景有:
·使用new關鍵字實例化對象的時候。
·讀取或設置一個類型的靜態字段(被final修飾、已在編譯期把結果放入常量池的靜態字段除外)的時候。
·調用一個類型的靜態方法的時候。
2)使用java.lang.reflect包的方法對類型進行反射調用的時候,如果類型沒有進行過初始化,則需要先觸發其初始化。
3)當初始化類的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類的初始化。
4)當虛擬機啓動時,用戶需要指定一個要執行的主類(包含main()方法的那個類),虛擬機會先初始化這個主類。
5)當使用JDK 7新加入的動態語言支持時,如果一個java.lang.invoke.MethodHandle實例最後的解析結果爲REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四種類型的方法句柄,並且這個方法句柄對應的類沒有進行過初始化,則需要先觸發其初始化。
6)當一個接口中定義了JDK 8新加入的默認方法(被default關鍵字修飾的接口方法)時,如果有這個接口的實現類發生了初始化,那該接口要在其之前被初始化。

 

被動引用不觸發初始化:

1)通過子類引用父類的靜態字段,不會導致子類初始化

2)通過數組定義來引用類,不會觸發此類的初始化

Java語言中對數組的訪問要比C/C++相對安全,很大程度上就是因爲這個類包裝了數組元素的訪問,而
C/C++中則是直接翻譯爲對數組指針的移動。在Java語言裏,當檢查到發生數組越界時會拋出
java.lang.ArrayIndexOutOfBoundsException異常,避免了直接造成非法內存訪問。

3)常量在編譯階段會存入調用類的常量池中,本質上沒有直接引用到定義常量的類,因此不會觸發定義常量的
類的初始化

注:接口也有初始化過程,這點與類是一致的,上面的代碼都是用靜態語句塊“static{}”來輸出初始化信息的,而接口中不能使
用“static{}”語句塊,但編譯器仍然會爲接口生成“<clinit>()”類構造器[2],用於初始化接口中所定義的成員變量。

7.3.1

在加載階段,Java虛擬機需要完成以下三件事情:
1)通過一個類的全限定名來獲取定義此類的二進制字節流。
2)將這個字節流所代表的靜態存儲結構轉化爲方法區的運行時數據結構。
3)在內存中生成一個代表這個類的java.lang.Class對象,作爲方法區這個類的各種數據的訪問入
口。

對於數組類而言,情況就有所不同,數組類本身不通過類加載器創建,它是由Java虛擬機直接在內存中動態構造出來的。但數組類與類加載器仍然有很密切的關係,因爲數組類的元素類型(ElementType,指的是數組去掉所有維度的類型)最終還是要靠類加載器來完成加載?????

 

7.3.2 驗證
驗證是連接階段的第一步,這一階段的目的是確保Class文件的字節流中包含的信息符合《Java虛擬機規範》的全部約束要求,保證這些信息被當作代碼運行後不會危害虛擬機自身的安全。

驗證階段是非常重要的,這個階段是否嚴謹,直接決定了Java虛擬機是否能承受惡意代碼的攻擊,從代碼量和耗費的執行性能的角度上講,驗證階段的工作量在虛擬機的類加載過程中佔了相當大的比重。

從整體上看,驗證階段大致上會完成下面四個階段的檢驗動作:文件格式驗證、元數據驗證、字節
碼驗證和符號引用驗證。

    4.符號引用驗證
最後一個階段的校驗行爲發生在虛擬機將符號引用轉化爲直接引用[3]的時候,這個轉化動作將在連接的第三階段——解析階段中發生。符號引用驗證可以看作是對類自身以外(常量池中的各種符號引用)的各類信息進行匹配性校驗,通俗來說就是,該類是否缺少或者被禁止訪問它依賴的某些外部類、方法、字段等資源

如果程序運行的全部代碼(包括自己編寫的、第三方包中的、從外部加載的、動態生成的等所有代碼)都已經被反覆使用和驗證過,在生產環境的實施階段就可以考慮使用-Xverify:none參數來關閉大部分的類驗證措施,以縮短虛擬機類加載的時間。

 

7.3.3 準備
準備階段是正式爲類中定義的變量(即靜態變量,被static修飾的變量)分配內存並設置類變量初始值的階段。

public static int value = 123;
變量value在準備階段過後的初始值爲0而不是123,因爲這時尚未開始執行任何Java方法,而把
value賦值爲123的putstatic指令是程序被編譯後,存放於類構造器<clinit>()方法之中,所以把value賦值
爲123的動作要到類的初始化階段纔會被執行。

如果類字段的字段屬性表中存在ConstantValue屬性,那在準備階段變量值就會被初始化爲ConstantValue屬性所指定
的初始值,假設上面類變量value的定義修改爲:
public static final int value = 123;
編譯時Javac將會爲value生成ConstantValue屬性,在準備階段虛擬機就會根據Con-stantValue的設置將value賦值爲123。

 

7.3.4 解析
解析階段是Java虛擬機將常量池內的符號引用替換爲直接引用的過程。

·符號引用(Symbolic References):符號引用以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時能無歧義地定位到目標即可。符號引用與虛擬機實現的內存佈局無關,引用的目標並不一定是已經加載到虛擬機內存當中的內容。各種虛擬機實現的內存佈局可以各不相同,但是它們能接受的符號引用必須都是一致的,因爲符號引用的字面量形式明確定義在《Java虛擬機規範》的Class文件格式中。
·直接引用(Direct References):直接引用是可以直接指向目標的指針、相對偏移量或者是一個能間接定位到目標的句柄。直接引用是和虛擬機實現的內存佈局直接相關的,同一個符號引用在不同虛擬機實例上翻譯出來的直接引用一般不會相同。如果有了直接引用,那引用的目標必定已經在虛擬機的內存中存在。

 

 

 

 

 

 

 

 

 

 

 

 

===========

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章