什么是類加載
? ??Java 類加載是指將 Java 字節碼文件加載到 Java 虛擬機(JVM)中,并將其轉化為可以執行的可執行代碼的過程。當 Java 程序在運行時引用某個類時,JVM 會首先檢查是否已經加載該類,如果沒有加載,則會通過類加載器加載該類。
類加載器負責加載字節碼文件(.class 文件)到 JVM 內存中,并生成一個 java.lang.Class 對象,該對象包含了類的所有信息。類加載器主要完成以下三個步驟:
-
加載:查找并加載類的字節碼文件。類加載器根據類的名稱來查找字節碼文件,然后將字節碼文件加載到內存中。
-
鏈接:將類的字節碼文件鏈接為可執行的代碼。鏈接過程包括驗證、準備(為靜態變量分配內存并設置初始值)、解析(將字節碼中的符號引用轉換為直接引用)等。
-
初始化:執行類的初始化代碼,包括靜態變量的初始化和靜態代碼塊的執行。
類加載器采用了雙親委派模型,即先委派給上層的父類加載器進行加載,如果父類加載器無法加載,則由當前類加載器進行加載。這個機制可以確保類的加載是有序的,并且可以避免重復加載同一個類。
雙親委派模型
? ? ??在Java中,類加載器負責將字節碼文件加載到內存中并創建對應的Class對象,從而使得Java程序可以使用這些類。雙親委派模型是Java類加載機制的一種設計模式,它通過層次化的類加載器結構來保證類的唯一性和安全性。
具體來說,雙親委派模型的原理如下:
- 當程序需要加載一個類時,首先會委托給最頂層的類加載器——啟動類加載器(Bootstrap Class Loader)。
- 啟動類加載器會檢查是否能夠加載該類,如果能夠加載,則直接進行加載;如果不能加載,則將加載請求委托給擴展類加載器(Extension Class Loader)。
- 擴展類加載器會檢查是否能夠加載該類,如果能夠加載,則直接進行加載;如果不能加載,則將加載請求委托給應用程序類加載器(Application Class Loader)。
- 應用程序類加載器會檢查是否能夠加載該類,如果能夠加載,則直接進行加載;如果不能加載,則拋出ClassNotFoundException異常。
? ? ?這種層次化的委派機制使得每個類加載器只負責加載自己負責的類,而不負責加載其他類。這種分工明確的加載機制可以有效地避免類的重復加載和被惡意篡改的風險。? ? ??
- 啟動類加載器(Bootstrap Class Loader)是最頂層的類加載器,它負責加載Java核心類庫,例如java.lang包下的類。
- 擴展類加載器(Extension Class Loader)是在啟動類加載器之后,負責加載Java擴展類庫,例如javax包下的類。
- 應用程序類加載器(Application Class Loader)是在擴展類加載器之后,負責加載應用程序的類。、
- 加載類時的順序是:啟動類加載器 -> 擴展類加載器 -> 應用程序類加載器。
另外,雙親委派模型還有以下幾個特點:
- 類加載器之間形成了父子關系,每個類加載器都有一個父加載器。子類加載器會首先委托給父加載器進行加載,只有在父加載器無法加載時,才會嘗試自己加載。
- 父加載器通過委派給子加載器,實現了類加載的向上委托機制。這樣可以確保類加載器在加載類時,先從上層類加載器查找,從而保證系統核心庫的安全性。
- 雙親委派模型中的類加載器是優先加載父類路徑下的類,這樣可以避免自定義類和Java核心類庫的沖突。
? ? ?雙親委派模型的原理是通過層次化的類加載器結構,自底向上地查找并加載類,從而保證類的唯一性和安全性。這種設計模式在Java中被廣泛應用,能夠有效避免類的重復加載和被篡改的風險,提高了系統的穩定性和安全性。
Java 類加載過程
? ? ?
Java類加載的過程可以分為以下7個步驟:
-
加載(Load):加載是類加載過程的第一步。在這一步,Java虛擬機會根據類的全限定名(包括包路徑)查找并加載類的二進制數據,并將其存放在方法區(Java 8之前)或元空間(Java 8及以后)中。類的二進制數據可以來自于多種來源,如本地文件系統、網絡、ZIP壓縮文件等。
-
驗證(Verify):驗證是加載過程的第二步。在這一步,Java虛擬機會對類的二進制數據進行合法性校驗,確保其符合Java虛擬機規范。驗證過程包括文件格式驗證、元數據驗證、字節碼驗證和符號引用驗證。
-
準備(Prepare):準備是加載過程的第三步。在這一步,Java虛擬機會為類的靜態變量分配內存,并設置默認初始值。靜態變量存放在方法區(Java 8之前)或元空間(Java 8及以后)中。
-
解析(Resolve):解析是加載過程的第四步。在這一步,Java虛擬機會將類、接口、字段和方法的符號引用解析為直接引用。符號引用是一種符號化的引用,直接引用是可以直接指向內存中的對象。
-
初始化(Initialize):初始化是加載過程的第五步。在這一步,Java虛擬機會對類的靜態變量進行賦值和靜態代碼塊的執行。初始化是類被首次主動使用時觸發的,包括以下情況:創建類的實例、調用類的靜態方法、訪問類的靜態變量、反射調用類的方法和字段,以及初始值為常量的靜態變量(編譯期常量)。
-
使用(Use):使用是加載過程的第六步。在這一步,Java虛擬機會根據需要使用類,執行相應的字節碼指令。使用的過程中可以觸發類的初始化。
-
卸載(Unload):卸載是加載過程的最后一步。在這一步,Java虛擬機會卸載不再使用的類。類的卸載通常是由Java虛擬機的垃圾回收器判斷并觸發的,判斷標準包括:類的實例被全部回收、類的ClassLoader被回收、類的引用被全部清除。
下面是一個簡單的示例代碼,用于演示類加載過程的幾個階段:
public class MyClass {// 靜態變量public static int count = 0;// 靜態代碼塊static {System.out.println("靜態代碼塊執行");count = 10;}// 靜態方法public static void printCount() {System.out.println("count = " + count);}public static void main(String[] args) {MyClass.printCount();}
}
運行上述代碼,可以看到輸出結果為:
靜態代碼塊執行
count = 10
可以看出,類的加載過程中,靜態代碼塊在準備階段進行賦值,然后在初始化階段執行。
總結?
-
加載:在該階段,類的字節碼文件被加載到內存中,并被存儲在方法區中的運行時常量池內。
-
驗證:在驗證階段,對字節碼文件的合法性進行檢查,以確保它滿足Java虛擬機的規范要求,并且不會對虛擬機產生安全風險。
-
準備:在準備階段,為類的靜態變量分配內存,并設置默認初始值。
-
解析:在解析階段,將常量池中的符號引用轉換為直接引用。
-
初始化:在初始化階段,對類的靜態變量進行初始化,并執行靜態代碼塊。
-
使用:在使用階段,類被加載到內存中,并可以被其他類引用和使用。
-
卸載:在卸載階段,當類或類的ClassLoader不再被引用時,類被從內存中卸載。
注意:類的加載過程是按需進行的,即只有在使用到該類時才會觸發加載過程。
??