JVM類加載機制詳解(一)JVM類加載過程

首先Throws(拋出)幾個本身學習過程當中一直疑惑的問題:java

一、什麼是類加載?何時進行類加載?編程

二、什麼是類初始化?何時進行類初始化?數組

三、何時會爲變量分配內存?安全

四、何時會爲變量賦默認初值?何時會爲變量賦程序設定的初值?網絡

五、類加載器是什麼?數據結構

六、如何編寫一個自定義的類加載器?學習

 

首先,在代碼編譯後,就會生成JVM(Java虛擬機)可以識別的二進制字節流文件(*.class)。而JVM把Class文件中的類描述數據從文件加載到內存,並對數據進行校驗、轉換解析、初始化,使這些數據最終成爲能夠被JVM直接使用的Java類型,這個說來簡單但實際複雜的過程叫作JVM的類加載機制測試

 

Class文件中的「類」從加載到JVM內存中,到卸載出內存過程有七個生命週期階段。類加載機制包括了前五個階段。spa

以下圖所示:code

其中,加載、驗證、準備、初始化、卸載的開始順序是肯定的,注意,只是按順序開始,進行與結束的順序並不必定。解析階段可能在初始化以後開始。

 

另外,類加載無需等到程序中「首次使用」的時候纔開始,JVM預先加載某些類也是被容許的。(類加載的時機

 

1、類的加載

咱們日常說的加載大多不是指的類加載機制,只是類加載機制中的第一步加載。在這個階段,JVM主要完成三件事:

 

一、經過一個類的全限定名(包名與類名)來獲取定義此類的二進制字節流(Class文件)。而獲取的方式,能夠經過jar包、war包、網絡中獲取、JSP文件生成等方式。

二、將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構。這裏只是轉化了數據結構,並未合併數據。(方法區就是用來存放已被加載的類信息,常量,靜態變量,編譯後的代碼的運行時內存區域)

三、在內存中生成一個表明這個類的java.lang.Class對象,做爲方法區這個類的各類數據的訪問入口。這個Class對象並無規定是在Java堆內存中,它比較特殊,雖爲對象,但存放在方法區中。

 

2、類的鏈接

類的加載過程後生成了類的java.lang.Class對象,接着會進入鏈接階段,鏈接階段負責將類的二進制數據合併入JRE(Java運行時環境)中。類的鏈接大體分三個階段。

一、驗證:驗證被加載後的類是否有正確的結構,類數據是否會符合虛擬機的要求,確保不會危害虛擬機安全。

二、準備:爲類的靜態變量(static filed)在方法區分配內存,並賦默認初值(0值或null值)。如static int a = 100;

靜態變量a就會在準備階段被賦默認值0。

對於通常的成員變量是在類實例化時候,隨對象一塊兒分配在堆內存中。

另外,靜態常量(static final filed)會在準備階段賦程序設定的初值,如static final int a = 666;  靜態常量a就會在準備階段被直接賦值爲666,對於靜態變量,這個操做是在初始化階段進行的。

完成了驗證階段以後,就進入準備階段。準備階段是正式爲變量分配內存空間而且設置類變量初始值

須要注意的是,這時候進行內存分配的僅僅是類變量(也就是被static修飾的變量),實例變量是不包括的,實例變量的初始化是在對象實例化的時候進行初始化,並且分配的內存區域是Java堆。這裏的初始值也就是在編程中默認值,也就是零值。

例如public static int value = 123 ;value在準備階段後的初始值是0而不是123,由於此時還沒有執行任何的Java方法,而把value賦值爲123的putStatic指令是程序被編譯後,存放在類構造器clinit()方法之中,把value賦值爲123的動做將在初始化階段纔會執行。

特殊狀況:若是類字段的字段屬性表中存在ConstantValue屬性,那在準備階段變量就會被初始化爲ConstantValue屬性所指定的值,例如public static final int value = 123 編譯時javac將會爲value生成ConstantValue屬性,在準備階段虛擬機就會根據ConstantValue的設置將變量賦值爲123。

三、解析:將類的二進制數據中的符號引用換爲直接引用。

 

3、類的初始化

類初始化是類加載的最後一步,除了加載階段,用戶能夠經過自定義的類加載器參與,其餘階段都徹底由虛擬機主導和控制。到了初始化階段才真正執行Java代碼。

類的初始化的主要工做是爲靜態變量賦程序設定的初值。

如static int a = 100;在準備階段,a被賦默認值0,在初始化階段就會被賦值爲100。

 

Java虛擬機規範中嚴格規定了有且只有五種狀況必須對類進行初始化

一、使用new字節碼指令建立類的實例,或者使用getstatic、putstatic讀取或設置一個靜態字段的值(放入常量池中的常量除外),或者調用一個靜態方法的時候,對應類必須進行過初始化。

二、經過java.lang.reflect包的方法對類進行反射調用的時候,若是類沒有進行過初始化,則要首先進行初始化。

三、當初始化一個類的時候,若是發現其父類沒有進行過初始化,則首先觸發父類初始化。

四、當虛擬機啓動時,用戶須要指定一個主類(包含main()方法的類),虛擬機會首先初始化這個類。

五、使用jdk1.7的動態語言支持時,若是一個java.lang.invoke.MethodHandle實例最後的解析結果REF_getStatic、REF_putStatic、RE_invokeStatic的方法句柄,而且這個方法句柄對應的類沒有進行初始化,則須要先觸發其初始化。

 

注意,虛擬機規範使用了「有且只有」這個詞描述,這五種狀況被稱爲「主動引用」,除了這五種狀況,全部其餘的類引用方式都不會觸發類初始化,被稱爲「被動引用」。

 

被動引用的例子一:

經過子類引用父類的靜態字段,對於父類屬於「主動引用」的第一種狀況,對於子類,沒有符合「主動引用」的狀況,故子類不會進行初始化。代碼以下:

 

 
  1. //父類

  2. public class SuperClass {

  3. //靜態變量value

  4. public static int value = 666;

  5. //靜態塊,父類初始化時會調用

  6. static{

  7. System.out.println("父類初始化!");

  8. }

  9. }

  10.  
  11. //子類

  12. public class SubClass extends SuperClass{

  13. //靜態塊,子類初始化時會調用

  14. static{

  15. System.out.println("子類初始化!");

  16. }

  17. }

  18.  
  19. //主類、測試類

  20. public class NotInit {

  21. public static void main(String[] args){

  22. System.out.println(SubClass.value);

  23. }

  24. }

輸出結果:

 

被動引用的例子之二:

經過數組來引用類,不會觸發類的初始化,由於是數組new,而類沒有被new,因此沒有觸發任何「主動引用」條款,屬於「被動引用」。代碼以下:

 

 
  1. //父類

  2. public class SuperClass {

  3. //靜態變量value

  4. public static int value = 666;

  5. //靜態塊,父類初始化時會調用

  6. static{

  7. System.out.println("父類初始化!");

  8. }

  9. }

  10.  
  11. //主類、測試類

  12. public class NotInit {

  13. public static void main(String[] args){

  14. SuperClass[] test = new SuperClass[10];

  15. }

  16. }

 

 

沒有任何結果輸出!

 

 

被動引用的例子之三:

剛剛講解時也提到,靜態常量在編譯階段就會被存入調用類的常量池中,不會引用到定義常量的類,這是一個特例,須要特別記憶,不會觸發類的初始化!

 

 
  1. //常量類

  2. public class ConstClass {

  3. static{

  4. System.out.println("常量類初始化!");

  5. }

  6.  
  7. public static final String HELLOWORLD = "hello world!";

  8. }

  9.  
  10. //主類、測試類

  11. public class NotInit {

  12. public static void main(String[] args){

  13. System.out.println(ConstClass.HELLOWORLD);

  14. }

  15. }

 

相關文章
相關標籤/搜索