Java中類的加載順序介紹(ClassLoader)

Java中類的加載順序介紹(ClassLoader)


1、ClassNotFoundExcetpion 
  我們在開發中,經常可以遇見java.lang.ClassNotFoundExcetpion這個異常,今天我就來總結一下這個問題。對於這個異常,它實質涉及到了java技術體系中的類加載。Java的類加載機制是技術體系中比較核心的部分,雖然它和我們直接打交道不多,但是對其背後的機理有一定理解有助於我們排查程序中出現的類加載失敗等技術問題。 
2、類的加載過程 
 一個java文件從被加載到被卸載這個生命過程,總共要經歷5個階段,JVM將類加載過程分爲: (加鏈初使卸)
  加載->鏈接(驗證+準備+解析)->初始化(使用前的準備)->使用->卸載 
(1)加載 
  首先通過一個類的全限定名來獲取此類的二進制字節流;其次將這個字節流所代表的靜態存儲結構轉化爲方法區的運行時數據結構;最後在java堆中生成一個代表這個類的Class對象,作爲方法區這些數據的訪問入口。總的來說就是查找並加載類的二進制數據。 
(2)鏈接: 
  驗證:確保被加載類的正確性; 
  準備:爲類的靜態變量分配內存,並將其初始化爲默認值; 
  解析:把類中的符號引用轉換爲直接引用; 
(3)爲類的靜態變量賦予正確的初始值 
3、類的初始化 
(1)類什麼時候才被初始化 
  1)創建類的實例,也就是new一個對象 
  2)訪問某個類或接口的靜態變量,或者對該靜態變量賦值 
  3)調用類的靜態方法 
  4)反射(Class.forName(“com.lyj.load”)) 
  5)初始化一個類的子類(會首先初始化子類的父類) 
  6)JVM啓動時標明的啓動類,即文件名和類名相同的那個類 
(2)類的初始化順序 
  1)如果這個類還沒有被加載和鏈接,那先進行加載和鏈接 
  2)假如這個類存在直接父類,並且這個類還沒有被初始化(注意:在一個類加載器中,類只能初始化一次),那就初始化直接的父類(不適用於接口) 
  3)加入類中存在初始化語句(如static變量和static塊),那就依次執行這些初始化語句。 

  4)總的來說,初始化順序依次是:(靜態變量、靜態初始化塊)–>(變量、初始化塊)–> 構造器

如果有父類,則順序是:父類static方法 –> 子類static方法 –> 父類構造方法- -> 子類構造方法 

4、類的加載 
  類的加載指的是將類的.class文件中的二進制數據讀入到內存中,將其放在運行時數據區的方法區內,然後在堆區創建一個這個類的java.lang.Class對象,用來封裝類在方法區類的對象。如: 
這裏寫圖片描述 
這裏寫圖片描述

  類的加載的最終產品是位於堆區中的Class對象。Class對象封裝了類在方法區內的數據結構,並且向Java程序員提供了訪問方法區內的數據結構的接口。加載類的方式有以下幾種: 
  1)從本地系統直接加載 
  2)通過網絡下載.class文件 
  3)從zip,jar等歸檔文件中加載.class文件 
  4)從專有數據庫中提取.class文件 
  5)將Java源文件動態編譯爲.class文件(服務器) 
5、加載器 
  JVM的類加載是通過ClassLoader及其子類來完成的,類的層次關係和加載順序可以由下圖來描述: 
   
這裏寫圖片描述

(1)加載器介紹 
1)BootstrapClassLoader(啓動類加載器) 
  負責加載$JAVA_HOME中jre/lib/rt.jar裏所有的class,加載System.getProperty(“sun.boot.class.path”)所指定的路徑或jar。 
2)ExtensionClassLoader(標準擴展類加載器) 
  負責加載java平臺中擴展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目錄下的jar包。載System.getProperty(“java.ext.dirs”)所指定的路徑或jar。 
3)AppClassLoader(系統類加載器) 
  負責記載classpath中指定的jar包及目錄中class 
4)CustomClassLoader(自定義加載器) 
  屬於應用程序根據自身需要自定義的ClassLoader,如tomcat、jboss都會根據j2ee規範自行實現。

(2)類加載器的順序 
1)加載過程中會先檢查類是否被已加載,檢查順序是自底向上,從Custom ClassLoader到BootStrap ClassLoader逐層檢查,只要某個classloader已加載就視爲已加載此類,保證此類只所有ClassLoader加載一次。而加載的順序是自頂向下,也就是由上層來逐層嘗試加載此類。 
2)在加載類時,每個類加載器會將加載任務上交給其父,如果其父找不到,再由自己去加載。 

3)Bootstrap Loader(啓動類加載器)是最頂級的類加載器了,其父加載器爲null。

參考博文:https://blog.csdn.net/eff666/article/details/52203406

     例子補充:

繼承的加載順序

由於static塊會在首次加載類的時候執行,因此下面的例子就是用static塊來測試類的加載順序。

package xing.test.thinking.chap7;
class A{
    static{
        System.out.println("A static");
    }
}
class B extends A{
    static{
        System.out.println("B static");
    }
}
class C extends B{
    private static D d = new D();
    static{
        System.out.println("C static");
    }
}
class D{
    static{
        System.out.println("D static");
    }
}
public class ExtendTest {
    public static void main(String[] args) {
        C c = new C();
    }
}

在上面的例子中,類C繼承B,B繼承A,而C有依賴於D。因此當創建C的時候,會自動加載C繼承的B和依賴的D,然後B又會加載繼承的A。只有A加載完,才能順利的加載B;BD加載完,才能加載C。這就是類的加載順序了。

A static
B static
D static
C static

所有的變量初始化完,纔會執行構造方法

在類的加載過程中,只有內部的變量創建完,纔會去執行這個類的構造方法

package xing.test.thinking.chap7;
class A2{
    B2 b2 = new B2();
    static{
        System.out.println("A static");
    }
    public A2() {
        System.out.println("A2()");
    }
}
class B2{
    C2 c2 = new C2();
    D2 d2 = new D2();
    static{
        System.out.println("B static");
    }
    public B2() {
        System.out.println("B2()");
    }
}
class C2{
    static{
        System.out.println("C static");
    }
    public C2() {
        System.out.println("C2()");
    }
}
class D2{
    static{
        System.out.println("D static");
    }
    public D2() {
        System.out.println("D2()");
    }
}
public class VarTest {
    public static void main(String[] args) {
        A2 a2 = new A2();
    }
}

在上面的例子中,A2裏面有B2變量,B2則有C2D2變量。因此類的加載還是先讀取到哪個,就執行相應的靜態塊。
當依賴的對象都定義完,纔會執行構造方法:

A static
B static
C static
C2()
D static
D2()
B2()
A2()

靜態成員與普通成員類的加載區別

在類的加載過程中,靜態成員類的對象,會優先加載;而普通成員類的對象則是使用的時候纔回去加載。

package xing.test.thinking.chap7;
class A3{
    B3 b3 = new B3();
    static C3 c4 = new C3();
    static{
        System.out.println("A3");
    }
}
class B3{
    static{
        System.out.println("B3");
    }
}
class C3{
    static{
        System.out.println("C3");
    }
}
public class StaticTest {
    public static void main(String[] args) {
        A3 a3 = new A3();
    }
}

輸出:

C3
A3
B3

總結

第一點,所有的類都會優先加載基類
第二點,靜態成員的初始化優先
第三點,成員初始化後,纔會執行構造方法
第四點,靜態成員的初始化與靜態塊的執行,發生在類加載的時候。
第四點,類對象的創建以及靜態塊的訪問,都會觸發類的加載。

補充類構造方法的順序

看代碼:

package xing.test.thinking.chap8;
class A{
    public A() {
        System.out.println("A");
    }
}
class B extends A{
    public B() {
        System.out.println("B");
    }
}
class C extends B {
    private D d1 = new D("d1");
    private D d2 = new D("d2");
    public C() {
        System.out.println("C");
    }
}
class D {
    public D(String str) {
        System.out.println("D "+str);
    }
}
public class ExtendTest {
    public static void main(String[] args) {
        C c = new C();
    }
}

執行結果:

A
B
D d1
D d2
C

因此可以得出結論:

  • 首先會調用基類的構造方法
  • 其次,調用成員的構造方法
  • 最後,調用自己的構造方法
參考博文:https://www.cnblogs.com/xing901022/p/5507086.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章