文章目錄
- 1. 類的加載、連結、初始化
-
- 1.1 類的加載
- 1.2 類的連結(驗證、準備、解析)
- 1.3 類的初始化
- 2. 類加載器
-
- 2.1 JVM類加載器分類
- 3. 類加載機制
- 4. 建立并使用自定義類加載器
- 5. 類加載的時機
- 6. 類的初始化
類的加載是指把類的.class檔案中的資料讀入到記憶體中。當程式需要使用某個類時,如果該類還未被加載到記憶體中,則JVM會通過機載、連結、初始化三個步驟對該類進行類加載。即JVM将類的加載過程分為三個步驟:加載(Load)、連結(Link)、初始化(Initialize),其中來連結又分為三步(驗證、準備、解析)
1. 類的加載、連結、初始化
1.1 類的加載
類加載指的是将類的class檔案讀入記憶體,并為之建立一個java.lang.Class對象。類的加載過程是由類加載器來完成,類加載器由JVM提供。我們開發人員可以通過繼承ClassLoader來實作自己的類加載器。
加載的class來源
- 從本地檔案系統内加載class檔案
- 從JAR包加載class檔案
- 通過網絡加載class檔案
- 把一個java源檔案動态編譯,并執行加載
1.2 類的連結(驗證、準備、解析)
通過類的加載,記憶體中已經建立了一個Class對象。而連結負責将二進制資料合并到JRE中。連結需要通過驗證、準備、解析三個階段。
- 驗證:驗證階段用于檢查被加載的類是否有正确的内部結構,是否符合JVM規範(檔案格式驗證、位元組碼驗證、符号引用驗證),即是否滿足JVM的限制。
- 準備:将類的靜态變量配置設定記憶體(配置設定在方法區),并将其初始化為預設值。
- 解析:将類中的符号引用轉換為直接引用
1.3 類的初始化
類的初始化階段,虛拟機主要對類變量進行初始化。虛拟機調用
<clinit>
方法,進行類變量的初始化。即将給類的靜态變量(類變量)賦予正确的初始值。
java類中對類變量進行初始化的兩種方式:
- 在定義時初始化
- 在靜态初始化塊内初始化
2. 類加載器
類加載器負責将.class檔案(不管是jar、本地磁盤、網絡擷取等)加載到記憶體中,并為之生成對應的java.lang.Class對象。
那如何判斷是同一個類呢?
每個類在JVM中使用全限定類名(包名+類名)與類加載器聯合為唯一的ID,是以如果同一個類使用不同的類加載器,可以被加載到虛拟機,但彼此不相容。
2.1 JVM類加載器分類
-
BootStarp ClassLoader(根加載器)
BootStarp ClassLoader是根類加載器,負責加載java的核心類庫(
下的jar包)。根加載器不是ClassLoader的子類,是由C++實作的。%JAVA_HOME%/jre/lib
-
Extension ClassLoader(擴充類加載器)
Extension ClassLoader是擴充類加載器,負責加載
或者%JAVA_HOME%/jre/ext
系統屬性所指定的目錄中jar包。可以将自己寫的工具包放到這個目錄下,可以友善自己使用。java.ext.dirs
-
Application/System ClassLoader(應用類加載器)
System ClassLoader是系統(應用)類加載器,負責加載來自java指令的-classpath選項、java.class.path系統屬性,或者CLASSPATH環境變量所指定的JAR包和類路徑。可以通過ClassLoader.getSystemClassLoader()來擷取系統類加載器。如果沒有特别指定,則使用者自定義的類加載器都以類加載器作為父加載器。
- 自定義加載器
3. 類加載機制
- 全盤負責機制:當一個加載器負責加載某個class時,該Class所依賴和引用的其他Class也由該類加載器負責載入,除非顯式使用另一個類加載器來加載。
- 父類委托/雙親委派機制:先讓父類加載器試圖加載該Class,如果父類加載器無法加載再自己加載;
- 緩存機制:緩存機制會将已經加載的Class緩存起來,當程式中需要使用某個Class檔案時,類加載器先從緩存區中搜尋該Class,隻有當緩存中不存在該Class時,系統才會讀取該類的二進制資料,并将其轉換為Class對象,存入緩存中。這就是為什麼更改了Class後,需要重新開機JVM才生效的原因(緩存失效)。
自定義加載器
=>
Application ClassLoader
=>
Extension ClassLoader
===>
Bootstarp ClassLoader
4. 建立并使用自定義類加載器
除了根類加載器,所有類加載器都是ClassLoader的子類。是以我們可以通過繼承ClassLoader來實作自己的類加載器。
ClassLoader類有兩個關鍵的方法:
-
:name為類名,resolve如果為true,在加載時解析該類。protected Class loadClass(String name,boolean resolve)
-
:根據指定類名來查找類。protected Class findClass(String name)
5. 類加載的時機
- new一個類的對象執行個體時
- 初始化類的子類時,會先加載父類
- 使用反射方法時,如
Class.forName("")
- 調用類的靜态變量時
- 調用類的靜态方式時
- 虛拟機啟動時指定執行的主類,會先執行初始化
6. 類的初始化
(1)類的初始化時間
- 建立了類的執行個體時,即new了一個對象
- 通路某個類或接口的靜态變量,或者對靜态變量指派
- 調用類的靜态方法
- 反射調用(Class.forName("…"))
- 初始化一個類的子類時,會首先初始化子類的父類
- JVM啟動時标明的啟動類,即檔案名和類名相同的類
(2)類的初始化步驟
- 如果這個類還沒有被加載和連結,那麼先進行加載和連結
- 加入這個類存在直接父類,并且這個類還沒有被初始化(注意:在一個類加載器中,類隻能初始化一次),那麼初始化直接父類(不适用于接口)
- 加入類中存在初始化語句(如static變量和static塊),那麼就依次執行這些初始化語句
(3)加載順序
同一個類中:靜态代碼塊==>非靜态代碼塊==>構造器
父子類中:父類==>子類
靜态代碼塊隻在第一次執行個體化(new)時運作,非靜态代碼塊在每次執行個體化時都運作。
代碼測試如下:
public class A {
static {
System.out.println("A中的靜态代碼塊");
}
{
System.out.println("A中的非靜态代碼塊");
}
public A() {
System.out.println("A的構造器");
}
}
public class B extends A{
static {
System.out.println("B中的靜态代碼塊");
}
{
System.out.println("B中的非靜态代碼塊");
}
public B() {
System.out.println("B的構造器");
}
public static void main(String[] args) {
A a=new A();
B b1=new B();
}
}
運作輸出結果如下:
A中的靜态代碼塊
B中的靜态代碼塊
A中的非靜态代碼塊
A的構造器
A中的非靜态代碼塊
A的構造器
B中的非靜态代碼塊
B的構造器
文章參考:請你詳細說說類加載流程,類加載機制及自定義類加載器