類加載機制

類加載機制

類加載機制是指 .class文件加載到JVM,并形成Class對象的機制。

類加載機制可以在運行時動態的加載外部的類、遠程網絡下載過來 class 文件等。除了動態的優點wai外,還可以通過JVM的類加載機制來達到類隔離的效果。

JVM將類加載劃分為三個步驟:

  • 裝載
  • 鏈接
  • 初始化

裝載和鏈接完成后就將二進制的字節碼轉換為 Class 對象;而初始化過程并不是加載類時必須觸發的(為什么呢),但是最遲必須在初次主動使用對象前執行。

類加載過程
類加載過程

裝載

裝載過程負責找到二進制字節碼并加載至JVM中,JVM通過類的全限定名類加載器完成類的加載,同樣,也采用全限定名類加載器來標識一個被加載了的類:類的全限定名 + ClassLoader實例ID。

類名的命名方式如下:

  • 對于接口或非數組型的類,其名稱即為類名,此種類型的類由所在的ClassLoader負責加載;
  • 對于數組型的類,其名稱為“[”+(基本類型|L)+引用類型類名;)
byte[] bytes = new byte[512];
System.out.println(bytes.getClass().getName());

Object[] objects = new Object[10];
System.out.println(objects.getClass().getName());

String[] strings = new String[10];
System.out.println(strings.getClass().getName());
# Output ---
[B
[Ljava.lang.Object;
[Ljava.lang.String;

鏈接

一:校驗 鏈接過程負責對二進制字節碼的格式進行校驗、初始化裝載類中的靜態變量及解析類中調用的接口和類。

校驗過程中如果碰到要引用到其他的接口和類,也會進行加載;如果加載過程失敗,則會拋出NoClassDefFoundError。

二:準備 在完成了校驗后,JVM初始化類中的靜態變量,并將其值賦為默認值。

三:解析 最后對類中的所有屬性、方法進行驗證,以確保其要調用的屬性、方法存在,以及具備相應的權限(例如public、private域權限等)。如果這個階段失敗,可能會造成NoSuchMethodEr-ror、NoSuchFieldError等錯誤信息。

初始化

初始化過程即執行類中的靜態初始化代碼、構造器代碼及靜態屬性的初始化。

以下四種情況下初始化過程會被觸發執行:

  1. 調用了new;
  2. 反射調用了類中的方法;
  3. 子類調用了初始化;
  4. JVM啟動過程中指定的初始化類。

在執行初始化過程之前,首先必須完成鏈接過程中的校驗和準備階段,解析階段則不強制。

JVM的類加載通過ClassLoader及其子類來完成:

  • Bootstrap ClassLoader 在代碼中沒有辦法拿到這個對象,Sun JDK啟動時會初始化此ClassLoader,并由ClassLoader加載$JAVA_HOME/jre/lib/rt.jar里所有class文件;
  • Extension ClassLoader JVM用此ClassLoader來加載擴展功能的一些jar包 $JAVA_HOME/jre/lib/ext/*.jar;
  • System ClassLoader(AppClassLoader) JVM用此ClassLoader來加載啟動參數中指定的Classpath中的jar包及目錄,在Sun JDK中ClassLoader對應的類名為AppClassLoader;
  • UserDefined ClassLoader 繼承Class-Loader抽象類自行實現的ClassLoader,基于自定義的ClassLoader可用于加載非Classpath中(例如從網絡上下載的jar或二進制)的jar及目錄、還可以在加載之前對class文件做一些動作(例如解密等)。
public class ClassTest {

    @Test
    public void testClassLoader() {
        System.out.println(ClassTest.class.getClassLoader());
        System.out.println(ClassTest.class.getClassLoader().getParent());
        System.out.println(ClassTest.class.getClassLoader().getParent().getParent());
    }
}

# Output ---
sun.misc.Launcher$AppClassLoader@3836b1bb
sun.misc.Launcher$ExtClassLoader@ece88d2
null

ClassLoader繼承關系
ClassLoader繼承關系

JVM的ClassLoader采用的是樹形結構,除BootstrapClass-Loader外,其他的ClassLoader都會有parent ClassLoader,UserDefined ClassLoader默認的parent ClassLoader為System ClassLoader。加載類時通常按照樹形結構的原則來進行,也就是說,首先應從 UserDefined ClassLoader中嘗試進行加載,當par-ent中無法加載時,應再嘗試從System ClassLoader中進行加載,System ClassLoader同樣遵循此原則,在找不到的情況下會自動從其parent ClassLoader中進行加載。

JVM是采用類名+Classloader的實例來作為Class加載的判斷的,因此加載時不采用上面的順序也是可以的,例如加載時不去parent ClassLoader中尋找,而只在當前的ClassLoader中尋找,會造成樹上多個不同的ClassLoader中都加載了某Class,并且這些Class的實例對象都不相同。

當Java開發人員調用Class.forName來獲取一個對應名稱的Class對象時,JVM會從方法棧上尋找第一個ClassLoader,通常也就是執行Class.forName所在類的ClassLoader,并使用此ClassLoader來加載此名稱的類。


ClassNotFoundException 這是最常見的異常,產生這個異常的原因為在當前的ClassLoader中加載類時未找到類文件,對位于System ClassLoader的類很容易判斷,只要加載的類不在Classpath中。而對位于UserDefined ClassLoader的類則麻煩些,要具體查看這個ClassLoader加載類的過程,才能判斷此ClassLoader要從什么位置加載到此類。例如直接在代碼中執行Class.forName(“com.blue-davy.A”),而當前類的classloader下根本就沒有該類所在的jar或沒有該class文件,就會拋出ClassNotFoundException。

NoClassDefFoundError 該異常較之ClassNotFoundException更難處理一些,造成此異常的主要原因是加載的類中引用到的另外的類不存在,如下:要加載A,而A中調用了B,B不存在或當前ClassLoader沒法加載B,就會拋出這個異常。當采用Class.forName加載A時,雖能找到A.class,但此時B.class不存在,則會拋出NoClassDefFoundError。

public class A {  
private B b=new B();
    
}

ClassCastException 這個異常比較難查的是兩個A對象由不同的ClassLoader加載的情況,這時如果將其中某個A對象造型成另外一個A對象,也會報出ClassCastException。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Java的核心是 JVM ,了解并熟悉JVM對于我們理解Java語言非常重要。 一、類加載機制 當程序主動使用某個...
    年少懵懂丶流年夢閱讀 1,102評論 2 15
  • JVM類加載機制 概述 類加載過程 加載 通過類的全限定名獲取類的二進制流 將靜態存儲結構轉化為方法區的運行時數據...
    東溪95閱讀 3,081評論 0 15
  • 在編寫 Java 程序時,我們所編寫的 .java 文件經編譯后,生成能被 JVM 識別的 .class 文件,....
    EricAlpha閱讀 3,660評論 0 6
  • 官文鏈接:UICollectionViewLayout - UIKit The UICollectionViewL...
    lixiaoshuai閱讀 206評論 0 0
  • 很幸運,我家寶貝,從出生直到現在,都是我自己在帶。對孩子的脾氣,性格,能力,喜好,口味都很了解。 對照今天張老師所...
    驲舊閱讀 285評論 0 0