1. 程式人生 > >Java虛擬機器系列005

Java虛擬機器系列005

JVM類載入

本章節講解Class檔案中的資訊進入到虛擬機器後發生什麼變化

虛擬機器把描述的類的資料從Class檔案載入到記憶體,並對資料進行效驗,轉換解析和初始化,最終形成可以被虛擬機器直接使用的Java型別,這就是Java虛擬機器的類載入機制。

1.類載入的時機

類載入的生命週期包括:載入,驗證,準備,解析,初始化,使用,解除安裝7個階段。其中驗證,準備,解析3部分稱為連結。載入,驗證,準備,初始化,解除安裝這5個階段的順序是確定的,類的載入過程按照這種順序按部就班的開始,而解析階段不一定,注意的是,這些階段通常都是相互交叉的混合式進行,通常會在一個階段執行的過程中呼叫,啟用另一個階段。

對於載入階段,Java虛擬機器規範中並沒有進行強制約束,可以交給虛擬機器的具體實現來自由把握。但是對於初始化階段,虛擬機器規範則是嚴格規定了有且只有5種情況必須立即進行初始化。

(1)遇到new,getstatic,putstatic或invokestatic這4條位元組碼指令時,如果類沒有進行過初始化,則需要先觸發其初始化。生成這4條指令的最常見的場景是:①使用new關鍵字例項化物件②③讀取或者設定一個類的靜態欄位④呼叫一個類的靜態方法的時候。

(2)使用java.lang.reflect包的方法對類進行反射呼叫的時候。

(3)初始化一個類的時候,發現其父類還沒有初始化,則先觸發其父類的初始化。

(4)當虛擬機器啟動時,使用者需要指定一個要執行的主類,虛擬機器會先初始化這個主類。

(5)如果一個java.lang.invoke.methodHandle例項最後的解析結果REF_getStatic,REF_putStatic,REF_invokeStatic的方法的控制代碼,並且 這個方法控制代碼所對應的類沒有進行過初始化,則需要先觸發其初始化。

對於這5類會觸發進行初始化的場景,並稱之為對一個類進行的主動引用,除此之外,所有引用類的方式都不會觸發初始化,稱為被動引用。

2.類載入過程

(1)載入

在載入階段,虛擬機器需要完成以下3件事情,①通過一個類的全限定名來獲取定義此類的二進位制位元組流②將這個位元組流所代表的靜態儲存結構轉化為方法區的執行時資料結構③在記憶體中生成一個代表這個類的java.lang.Class物件,作為方法區這個類的各種資料的訪問入口。

對於第一件事情,Java虛擬機器實現的並不具體,靈活度相當大,他可以允許來自許多地方的二進位制位元組流,例如從ZIP包中讀取,即JAR,EAR,WAR格式的基礎;從網路中獲取,即Applet;執行時計算生成,即動態代理計數;由其他檔案生成,即JSP應用;從資料庫中獲取。

對於非陣列類的載入階段是開發人員可控制最強的,因為既可以使用系統提供的類載入器完成,也可以使用使用者自定義的類載入器去完成。

對於陣列類而言,陣列類本身不通過類載入器建立,而是由Java虛擬機器直接建立的。但是陣列類與類載入器還是有密切的關係,因為陣列類的元素型別,還是要靠類載入器去建立。

(2)驗證

驗證是為了確保Class檔案的位元組流中包含的資訊符合當前虛擬機器的要求,並且不會危害虛擬機器的安全。因為位元組碼檔案不僅僅只由Java程式碼編譯生成,還可以使用其他的程式碼生成,由於其他的程式碼的不安全性,所以需要進行驗證。

下來簡單介紹書中提及的4個階段的檢驗動作(這不是全部):

①檔案格式驗證:這一階段的驗證位元組流是否符合Class檔案格式的規範,並且被當前版本的虛擬機器處理。包括是否已魔數0xCAFFBABE開頭,主次版本號,常量池中是否有不被支援的常量型別。當驗證點不止這些,還有很多,上面只是一部分而已。

②元資料驗證:對位元組碼的語義分析,保證其描述的資訊符合Java語言規範的要求。即此類是否有父類,是否合法繼承等等。

③位元組碼驗證:最複雜的一個,目的是通過資料流和控制流分析,確定程式語義是合法的,符合邏輯的。

④符號引用驗證:對類自身以外的資訊進行是否匹配性效驗,即符號引用中通過字串描述的全限定名是否能找到對應的類,符號引用中的類,欄位,方法的訪問性是否可被當前類訪問。

(3)準備

準備階段是正式為類變數分配記憶體並設定類變數初始值的階段,這些變數所使用的記憶體都將在方法區中進行分配。注意,這個時候進行記憶體分配的僅包括類變數,而不包括例項變數,例項變數將會在物件例項化時隨物件一起分配在Java堆中。

Public static int value = 123.

變數value在準備階段過後的初始值是0,不是123,因為尚未執行任何Java方法,而把value賦值為123的putstatic指令是程式被編譯後,存放於類構造器<clinit>()方法之中,所以把value賦值為123的動作將在初始化階段才會執行。

(4)解析

虛擬機器將常量池內的符號引用替換為直接引用的過程

符號引用:以一組符號來描述所引用的目標,符號可以是任意的字面量,只要使用時能夠無歧義定位到目標即可。

直接引用:直接指向目標的指標,相對偏移量或是一個能間接定位到目標的控制代碼。

解析包括類或介面的解析,欄位的解析,介面方法的解析。

(5)初始化

是類載入器過程的最後一步,在準備階段,變數已經賦過一次系統要求的初始值,而在初始化階段,則根據程式設計師通過程式制定的主觀計劃去初始化類變數和其他資源。

類載入器雖然只用於實現類的載入動作,但它在Java程式中起到的作用遠不止這些。對於任意一個類,都需要有載入它的類載入器和這個類的本身一同確立其在Java虛擬機器中的唯一性。每一個類載入器,都擁有一個獨立的類名稱空間。即比較兩個類是否相等,只有在這兩個類是由同一個類載入器載入的前提下才有意義,否則即使這兩個類來源於同一個Class檔案,被同一個虛擬機器載入,只要載入他們的類載入器不同,那這兩個類必定不相等。這裡的相等包括Class物件的equals()方法,isAssignableFrom()方法,isInstance()方法的返回結果。

3.雙親委派模型

從Java虛擬機器的角度來講,存在兩種不同的類載入器:一種是啟動類載入器(Bootstrap ClassLoader),使用C++語言實現,是虛擬機器的一部分;另一種是所有其他的類載入器,由Java語言實現,獨立與虛擬機器外部,並且全部繼承自抽象類java.lang.ClassLoader。

從Java開發人員的角度來看,大多數程式設計師都會使用以下3中系統提供的類載入器:

①啟動類載入器(Bootstrap ClassLoader),這個載入器負責將存放在<JAVA_HOME>\lib目錄中的,或者被-Xbootclasspath引數所指定的路徑中的,並且是虛擬機器識別的類庫載入到虛擬機器記憶體中。

②擴充套件類載入器(Extension ClassLoader):負責<JAVA_HOME>\lib\ext目錄中的,或者被java.ext.dirs系統變數所指定的路徑中的所有類庫進行載入,開發者可以直接使用。

③所有程式類載入器(Application ClassLoader):是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也稱為系統類載入器。它負責載入使用者類路徑(ClassPath)上所指定的類庫,開發者可以直接使用這個類載入器。

雙親委派模型要求除了頂層的父類載入器外,其餘的類載入器都應當有自己的父類載入器,這裡的父子關係一般不是繼承,而是都使用組合關係來複用父類載入器的程式碼。

雙親委派模型的工作過程是:如果一個類載入器收到了類載入請求,它首先不會自己去嘗試載入這個類,而是把這個請求委派給父類載入器去完成,每一個層次的父類載入器都是如此,只有當父類載入器返回自己無法完成這個載入請求時,子載入器才會嘗試自己去在載入。

本章節介紹了虛擬機器的類載入過程,其中的更具體的內容還請讀者儘量看書,可以更進一步的理解。如有錯誤,敬請指出。