探索Java類的加載機制與初始化

加載生命週期
類從加載進入JVM內存到卸載出內存它經歷的完整的生命週期是:加載、驗證、準備、解析、初始化、使用、卸載。
加載過程
類的加載過程包括:加載、驗證、準備、解析、初始化共五個階段。加載過程需要注意的是:
1、解析的發生順序是不確定的,因爲Java支持動態綁定,解析也可能發生在初始化開始之後進行。
補充:Java的綁定分爲靜態和動態:靜態綁定爲Java編譯期綁定,在Java中只有static、final、private以及構造方法是靜態綁定,其他大部分都是動態綁定
2、除解析以外的其他是個過程的發生順序是確定,但是並不能說它是按順序進行或者完成的,因爲解析過程可能發生互相調用,交叉進行的情況。
加載過程逐一解析
1、加載:
加載主要幹3件事:
1、獲取類的字節流:根據類的全限定名獲取其定義的二進制字節流(不限於從Class文件中獲取,還可以從jar等其他中獲取)
2、結構轉換:將這個二進制字節流代表的靜態存儲結構轉換成方法區的運行時數據結構
3、在Java堆內存中生成一個對應的Class類對象,作爲方法區數據訪問入口
*補充:
在Java類的加載階段相對於其他階段來說,加載階段的可控性是最強的,因爲在這一階段,我們不僅可以使用Java提供的類加載器,我們還能夠自定義類加載器。

  • 對於JVM來說,類加載器可以分爲兩類:

    1 啓動加載器:它是JVM本身的一部分
    2 其他所有的類加載器:這些加載器都繼承自Java.lang.ClassLoader,它們獨立於JVM之外,這些類加載器需要啓動加載器來加載它們,然後它們才能夠去加載其他的類

  • 對於開發人員來說,類加載器可以分爲三類:
    1、啓動加載器:BootStrap ClassLoader,它負責加載存放在JDK\jre\li(JDK 代表 JDK 的安裝目錄,下同)下,或被-Xbootclasspath參數指定的路徑中的,並且能被虛擬機識別的類庫(如 rt.jar,所有的java.*開頭的類均被 Bootstrap ClassLoader 加載)。啓動類加載器是無法被 Java 程序直接引用的。
    2、擴展類加載器:Extension ClassLoader,該加載器由sun.misc.Launcher$ExtClassLoader實現,它負責加載JDK\jre\lib\ext目錄中,或者由 java.ext.dirs 系統變量指定的路徑中的所有類庫(如javax.*開頭的類),開發者可以直接使用擴展類加載器。

    3、應用程序類加載器:Application ClassLoader,該類加載器由 sun.misc.Launcher$AppClassLoader 來實現,它負責加載用戶類路徑(ClassPath)所指定的類,開發者可以直接使用該類加載器,如果應用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認的類加載器

如果我們遇到一下需求:

  • 在執行非置信代碼前,自動驗證數字簽名
  • 動態地創建符合用戶特定需要的定製化構建類。
  • 從特定的場所取得 java class,例如數據庫中和網絡中。(Applet就用到了自定義的class loader)

我們還可以自定義Class Loader,這幾種類加載器的關係是:
這裏寫圖片描述
類加載器使用了雙親委派模型,這種模式不是利用繼承來實現,而是用組合模式來複用父類中的代碼的,這種模型的工作原理是:
如果一個類加載器接受到一個類加載請求,那麼這個類不會立刻去加載類,它首先會把請求委託給父類去加載,依次向上,所以所有的請求都最終會由啓動類加載器來加載,如果父類無法加載,那麼才由子類來加載,依次向下。
對於任何一個類,JVM都要確定其唯一性,而唯一性的確定和該類以及加載該類的加載類有關,因此,加載類的雙親委派模型有一個明顯的好處就是使類加載器有一個優先級關係,這種優先級關係很好的保障了Java程序運行的穩定性,例如:一個類加載器接受到請求時,會依次向上委託,如果啓動類加載器能夠加載這個類,那麼就保證了所有的這種請求都是由啓動類加載來加載,這就保證了類在加載過程中的唯一性。
2、驗證
驗證是爲了保證此字節流所包含的信息符合虛擬機的要求,並且對虛擬機本身是安全的
驗證分爲以下四種:

  • 文件格式的驗證:驗證字節流是否符合 Class 文件格式的規範,並且能被當前版本的虛擬機處理,該驗證的主要目的是保證輸入的字節流能正確地解析並存儲於方法區之內。經過該階段的驗證後,字節流纔會進入內存的方法區中進行存儲,後面的三個驗證都是基於方法區的存儲結構進行的。
  • 元數據驗證:對類的元數據信息進行語義校驗(其實就是對類中的各數據類型進行語法校驗),保證不存在不符合 Java 語法規範的元數據信息。
  • 字節碼驗證:該階段驗證的主要工作是進行數據流和控制流分析,對類的方法體進行校驗分析,以保證被校驗的類的方法在運行時不會做出危害虛擬機安全的行爲。
  • 符號引用驗證:這是最後一個階段的驗證,它發生在虛擬機將符號引用轉化爲直接引用的時候,主要是對類自身以外的信息(常量池中的各種符號引用)進行匹配性的校驗。

3、準備
是爲類變量分配內存並設置類變量初始值的階段,這個時候的內存分配僅包括static類變量,而不包括實例變量,實例變量是在實例化得時候進行內存分配
4、解析
解析階段是虛擬機將常量池中的符號引用轉化爲直接引用的過程
解析動作主要針對類或接口、字段、類方法、接口方法四類符號引用進行,分別對應於常量池中的 CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info、CONSTANT_InterfaceMethodref_info 四種常量類型。
5、初始化

  • 初始化的時間:是類加載過程的最後階段,當發生以下情況的任意一種的時候都會觸發類的初始化:
    1、通過getstatic、putstatic、invokestatic指令,或者調用一個static方法,或者靜態域被賦值的時候會初始化
    2、通過new關鍵字或者調用Java.lang.refect 包中的反射方法時
    3、初始化一個類時,會先初始化其super類
    4、當虛擬機啓動時,用戶需要指定一個要執行的主類,虛擬機會先執行該主類。
    虛擬機規定只有這四種情況纔會觸發類的初始化,稱爲對一個類進行主動引用,除此之外所有引用類的方式都不會觸發其初始化,稱爲被動引用。下面舉一些例子來說明被動引用。

    通過子類引用父類中的靜態字段,這時對子類的引用爲被動引用,因此不會初始化子類,只會初始化父類:

class Father{  
    public static int m = 33;  
    static{  
        System.out.println("父類被初始化");  
    }  
}  

class Child extends Father{  
    static{  
        System.out.println("子類被初始化");  
    }  
}  

public class StaticTest{  
    public static void main(String[] args){  
        System.out.println(Child.m);  
    }  
} 

結果:
父類被初始化
33

參考資料:

1. http://www.infoq.com/cn/articles/cf-Java-class-loader
2. http://wiki.jikexueyuan.com/project/java-vm/
3. http://www.importnew.com/6579.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章