天天看點

探索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,這幾種類加載器的關系是:

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

類加載器使用了雙親委派模型,這種模式不是利用繼承來實作,而是用組合模式來複用父類中的代碼的,這種模型的工作原理是:

如果一個類加載器接受到一個類加載請求,那麼這個類不會立刻去加載類,它首先會把請求委托給父類去加載,依次向上,是以所有的請求都最終會由啟動類加載器來加載,如果父類無法加載,那麼才由子類來加載,依次向下。

對于任何一個類,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 = ;  
    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

參考資料:

. http://www.infoq.com/cn/articles/cf-Java-class-loader
. http://wiki.jikexueyuan.com/project/java-vm/
. http://www.importnew.com/.html