1. 程式人生 > >類載入器及類載入的過程

類載入器及類載入的過程

類載入器及類載入的過程

類載入器

虛擬機器設計團隊把類載入階段中的“通過一個類的許可權定名來獲取此類的二進位制位元組流”這個動作放到Java虛擬機器外部去實現,以便讓程式自己決定如何去獲取所需要的類。實現這個動作的程式碼模組稱為“類載入器”。
類載入器可以說是Java語言的一項創新,也是Java語言流行的重要原因至於,它最初是為了滿足Java Applet的需求而開發出來的。雖然目前Java Applet技術基本已經“死掉”,但類載入器卻在類層次劃分、OSGI、熱部署、程式碼加密等領域大放異彩,稱為了Java技術體系中的一塊重要的基石,可謂是失之桑榆,收之東隅。

類與類載入器

類載入器雖然只用於實現類的載入動作,但它在Java程式中起到的作用卻遠遠不止與類載入的階段。對於任意一個類,都需要由載入它的類載入器和這個類本身一同確立其在Java虛擬機器中的唯一性,每一個類載入器,都擁有一個獨立的類名稱空間。**比較兩個類是否“相等”,只有在這兩個類是由同一個類載入器載入的前提下才有意義。**否則,即使這兩個類來源於同一個Class檔案,被同一個虛擬機器載入,只要載入它們的類載入器不同,那麼這兩個類就必定不相等。

雙親委派模型

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

圖中所展示的類載入器之間的這種層次關係,稱為類載入器的雙親委派模型(Parent Delegation Model)。雙親委派模型要求除了頂層的啟動類載入器,其餘的類載入器都應該有自己的父類載入器。這裡的載入器之間的父子關係一般不會以繼承的關係來實現,而是都使用組合的關係來複用父類載入器的程式碼。
在這裡插入圖片描述
1.啟動類載入器(Bootstrap ClassLoader):這個類載入器負責將存放在<JAVA_HOME>\lib目錄中的,或者被-Xbootclasspath引數所指定的路徑中的,並且是虛擬機器識別的類庫載入到虛擬機器記憶體中。啟動類載入器無法被Java程式直接引用,使用者在編寫自定義類載入器中,如果需要把載入請求委派給引導類載入器,那直接使用null代替即可。

載入JVM執行以來的jar包

2.擴充套件類載入器(Extension ClassLoader):這個類載入器由sun.misc.Launcher$ExtClassLoader實現,他負責載入<JAVA_HOME>\lib\ext,目錄中的,或者被java.ext.dirs系統變數所指定的路徑中的所有類庫,開發者可以直接使用擴充套件類載入器。

載入jdk=>jvm裡面lib目錄中的ext中的jar包

3.應用程式類載入器(Application ClassLoader):這個類載入器由sun.misc.Launcher$App-ClassLoader實現。由於這個類載入器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也稱它為系統類載入器。它負責載入使用者類路徑(ClassPath)上所指定的類庫,開發者可以直接使用這個類載入器,如果應用程式類載入器中沒有自定義過自己的類載入器,一般情況下這個就是程式中預設的類載入器。

根據ClassPath指定路徑,尋找應用程式所有載入class位元組碼檔案,從位元組碼檔案中載入型別對應的class物件

類載入器的時機

類從被載入到虛擬機器記憶體中開始,到卸載出記憶體為止,它的整個生命週期包括:載入、驗證、準備、解析、初始化、使用和解除安裝7個階段。其中驗證、準備、解析3個部分統稱為連線。
在這裡插入圖片描述
其中只有載入、驗證、準備、初始化、和解除安裝這5個階段的順序是確定的。
那麼什麼情況下需要開始類載入過程的第一個階段:載入?Java虛擬機器規範中並沒有進行強制約束和,這個點可以交由虛擬機器具體實現來自由把握。但是對於初始化階段,虛擬機器規範則是嚴格規定了五中情況必須立刻對類進行初始化:

1.遇到new、getstatic、putstatic、或者invokestatic這4條位元組碼指令時,如果類沒有進行過初始化,則需要先觸發其初始化。生成這4條指令的最常見的Java程式碼場景是:使用new關鍵字例項化物件的時候、讀取或設定一個類的靜態欄位的時候,以及呼叫一個類的靜態方法的時候。
2.使用java.lang.reflect包的方法對類進行反射呼叫時候。
3.當初始化一個類的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類的初始化。
4.當虛擬機器啟動時,使用者需要指定一個要執行的主類(包含main()方法的那個類),虛擬機器會先初始化這個主類。
5.當JDK 1.7的動態語言支援時,如果一個java.lang.invoke.MethodHandle例項最後的解析結果是REF_getStatic、REF_putStatic、REF_invokeStatic的方法控制代碼,並且這個方法控制代碼所對應的類沒有初始化。

類載入的過程

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

簡單來說就是通過類載入器在指定路徑下面,從class位元組碼檔案載入為class物件

2.驗證
驗證是連線階段的第一步,這一個階段的目的是為了確保Class檔案的位元組流中包含的資訊符合當前虛擬機器的要求,並且不會危害虛擬機器自身的安全。
(1)檔案格式驗證:第一階段要驗證位元組流是否符合Class檔案的規範,並且能被當前虛擬機器處理。
(2)元資料驗證:第二階段是對位元組碼描述的資訊進行語義分析,以保證其描述的資訊符合Java語言規範的要求。
(3)位元組碼驗證:第三階段是整個驗證過程中最為複雜的一個階段,主要目的是通過資料流和控制流分析,確定程式語義是合法的、符合邏輯的。在第二階段對元資料資訊中的資料型別做完校驗後,這個階段將對類的方法體進行校驗分析,保證被校驗類的方法再執行時不會做出危害虛擬機器的安全事件。

3.準備
準備過程是正式的為類變數分配記憶體並設定初始值的階段在,這些變數所使用的記憶體豆漿在方法區中進行分配。這個階段中有兩個容易產生混淆的概念要強調,首先,這個時候進行分配的僅包括類變數(被static修飾),而不包括例項變數,例項變數將會在物件例項化時隨著物件一起分配在Java堆中。其次,這裡所說的初始值“通常情況”下是資料型別為零。

4.解析
解析階段是虛擬機器將常量池內的符號引用替換為直接引用的過程。
(1)類或介面的解析
(2)欄位解析
(3)類方法解析
(4)介面方法解析

整個連線過程:對類中的靜態成員變數進行分配記憶體空間,如果有此基類,對此基類進行載入

5.初始化
類初始化階段是類載入器過程的最後一步,前面的類載入過程中,除了在載入階段使用者應用程式可以通過自定義類載入器參與之外,其餘動作完全由虛擬機器主導和控制。到了初始化階段,才真正開始執行類中定義的Java程式程式碼。
在準備階段,變數已經賦值過一次系統要求的初始值,而在初始化階段,則根據程式設計師通過程式制定的主觀計劃去初始化變數和其他資源,或許可以從另外一個角度去表達:初始化階段是執行類構造器()方法的過程。

對靜態成員變數進行初始化