天天看點

類加載過程、類加載器、類的初始化1. 類的加載、連結、初始化2. 類加載器3. 類加載機制4. 建立并使用自定義類加載器5. 類加載的時機6. 類的初始化

文章目錄

  • 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的核心類庫(

    %JAVA_HOME%/jre/lib

    下的jar包)。根加載器不是ClassLoader的子類,是由C++實作的。
  • Extension ClassLoader(擴充類加載器)

    Extension ClassLoader是擴充類加載器,負責加載

    %JAVA_HOME%/jre/ext

    或者

    java.ext.dirs

    系統屬性所指定的目錄中jar包。可以将自己寫的工具包放到這個目錄下,可以友善自己使用。
  • 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類有兩個關鍵的方法:

  • protected Class loadClass(String name,boolean resolve)

    :name為類名,resolve如果為true,在加載時解析該類。
  • 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的構造器
           

文章參考:請你詳細說說類加載流程,類加載機制及自定義類加載器