聊聊Java的類加載

網上介紹Java類加載的文章不計其數,但大多都千篇一律。之前有打算寫一下類加載,一直感覺自己理解不是很透徹,現在感覺可以出鍋了,哦,不對,可以出徒了,也不對,可以寫博了。廢話不多說,上乾貨。

不加例子的解釋都是耍流氓,先來個簡單的例子:

public class TestStatic {
    private static TestStatic tester = new TestStatic();
    private static int count1;
    private static int count2 = 2;

    public TestStatic() {
        count1++;
        count2++;
        System.out.println("" + count1 + "\t" + count2);
    }

    static {
        System.out.println("靜態代碼塊執行:" + count1);
    }
    {
        System.out.println("構造代碼塊執行");
    }
    public static TestStatic getTester() {
        System.out.println(count1 + "\t" + count2);
        return tester;
    }

    public static void main(String[] args) {
        System.out.println(TestStatic.getTester());
    }
}

這段代碼的執行結果是:

構造代碼塊執行
1 1
靜態代碼塊執行:1
1 2

如果答對的同學,可以下樹了,答錯的同學,聽我娓娓道來。

  • 首先經過類加載器加載,TestStatic對象的二進制字節流已進入內存。
  • TestStatic被jvm標記爲啓動類了(包含main函數),觸發類加載, 在鏈接階段的準備階段,變量tester的初始值爲null,count1爲0,count2也爲0,
  • 鏈接準備階段的賦值操作完成,觸發類加載的初始化階段。首先觸發類構造器執行,執行到TestStatic tester = new TestStatic();,實例構造器開始執行。故構造代碼塊首先執行,打印出:構造代碼塊執行。然後執行構造函數,count1和count2此時都是鏈接準備階段的賦值結果0,然後自增,故打印出1,1。tester賦值完畢,然後繼續執行,count1沒有重新賦值,還是1,count2被重新賦值,變成了2。然後執行靜態代碼塊,故打印出靜態代碼塊執行:1。至此,類加載完成。
  • 最後調用類的靜態方法,會觸發類加載,因爲此時TestStatic已經被jvm加載過,故不在重新加載,直接調用方法getTester,打印出 1 2

何時觸發類的加載

主動引用的類會觸發類加載機制,被動引用則不會觸發。

主動引用
  • 調用類的靜態屬性
  • 調用類的靜態方法
  • 使用反射加載類Class.forName("com.demo.Student")
  • 使用new關鍵字創建對象。
  • 被jvm標記爲啓動類main函數所在的類。
  • 初始化子類,若父類未被初始化,則首先初始化父類。
被動引用
  • 調用父類的靜態變量,不會觸發子類加載,只會加載父類
  • 通過一個類創建數組引用,Student[] students = new Student[]{10};
  • 調用類的常量

類是怎樣被加載

.java源文件被編譯成.class文件,通過Java的類加載器,把.class文件加載到jvm的方法區中,在堆中生成代表次類的Class對象,用來封裝方法區的數據結構。一個類無論產生多少對象,其Class對象只有這一個。

類加載器

  • 系統類加載器(Bootstrap ClassLoader
    Java最底層的類加載器,負責加載$JAVA_HOME/jre/lib/rt.jar中的class,String,Object,Integer等就是由該類加載器加載,由C++實現。
  • 擴展類加載器(Extension ClassLoade)
    加載Java平臺擴展的jar包,負責加載$JAVA_HOME/jre/lib/ext/*.jar中的class,或者-Djava.etx.dirs指定jar包中的類。
  • 應用類加載器(App Classloader
    最常用的類加載器,classpath中的類及目錄中的class。
  • 自定義類加載器
    根據需要,用戶自定義的類加載器,按照jvm規範。tomcat,jboss都根據規範實現了自己的classloader

雙親委派模型

如果一個類加載器收到了類加載請求,它並不會自己先去加載,而是把這個請求委託給父類的加載器去執行,如果父類加載器還存在其父類加載器,則進一步向上委託,依次遞歸,請求最終將到達頂層的啓動類加載器,如果父類加載器可以完成類加載任務,就成功返回,倘若父類加載器無法完成此加載任務,子加載器纔會嘗試自己去加載,這就是雙親委派模式。

雙親委派模型保證了Java的安全性,試想,如果你自定義了一個java.lang.Object,通過雙親委派模型,最終會讓系統類加載器加載,你自定義的類加載器不會被加載。保證了Object的安全性。另外有些j2ee服務器需要打破這種雙親委派模型。

類加載的三個步驟

1:加載

查找並加載類的二進制數據、將硬盤上的.class文件加載進內存中。

2:鏈接

鏈接又分爲三個步驟:

  • 驗證
    確保被加載類的正確性,也就是javac編譯的class文件的正確性
  • 準備(重要)
    爲類的靜態屬性賦初始值,int 初始值爲0,boolean初始值爲false,引用類型初始值爲null。特別注意:如果類變量被final修飾,此時直接賦值爲程序猿聲明的值。
  • 解析
    將類的符號引用轉化爲直接引用,也就是將對象的引用轉化爲指針。

3:初始化

常規情況:

  • 首先初始化類構造器,編譯器自動收集類中的靜態變量,靜態代碼塊合併產生類構造器,順序按照代碼中出現的順序
  • 然後初始化實例構造器,編譯器自動收集類中 實例變量賦值動作,實例代碼塊,構造函數,合併成實例構造器。

實力初始化並不一定在類構造器之後執行,有可能類初始化包含實例初始化,這句話非常重要,好好體會,便能明白上面的題的原理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章