類加載的時機java
一個類的生命週期包括:加載、驗證、準備、解析、初始化、使用、卸載這七個部分。其中的驗證、準備、解析這三個部分又能夠稱爲鏈接。
其中加載、驗證、準備、初始化和卸載這5個步驟是循序漸進去完成的,而解析這個階段,則不必定:它能夠在某些狀況下在初始化以後再進行解析,這是爲了支持Java語言的動態綁定而設計的。
當遇到下面的幾種情形時,必須對類進行初始化:(此時加載、驗證、準備這幾個階段必須在初始化以前開始)
1)當遇到new、putstatic、getstatic、invokestatic這四條指令時,若是一個類尚未初始化,則必需要觸發其初始化。
用到這四條指令的常見場景:用new關鍵字實例化一個對象、讀取或設置一個類的靜態變量(用final修飾的除外)、調用一個類的靜態方法
2)使用java.lang.reflect包裏面的方法對類進行反射調用的時候,若是該類還沒初始化,則須要先觸發其初始化
3)當須要初始化一個類的時候,若是其直接父類還沒初始化,則須要先觸發其直接父類的初始化(因此Object類永遠是最早初始化的一個類)
4)當虛擬機啓動的時候,用戶須要指定一個類做爲執行的主類(包含main方法的類),虛擬機會先初始化這個主類
5)當使用JDK 1.7的動態語言支持時,若是java.lang.invoke.MethodHandle實例最後的解析結果是REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,而且此方法句柄對應的類並無進行初始化,則須要觸發其初始化。
以上這5種情形,均可以稱之爲對一個類進行主動引用。除此以外,全部引用類的方式都不會觸發初始化,稱爲被動引用。數據結構
被動引用例子之一
e.g:佈局
public class SuperClass { static { System.out.println("This is super class"); } public static int value = 12; } public class SubClass extends SuperClass{ static { System.out.println("This is sub class"); } } public class Main { public static void main(String[] args) { System.out.println(SubClass.value); } }
以上代碼執行時,結果爲:優化
能夠看到,其中並無打印出SubClass裏面靜態代碼塊的內容,證實SubClass並未被初始化。由於這個靜態變量是在SuperClass中定義的,經過子類來引用父類定義的靜態變量,只會初始化父類,並不會初始化子類。spa
當須要初始化子類時,能夠把以上代碼稍做改動:設計
e.g:指針
public class Main { public static void main(String[] args) { System.out.println(new SubClass().value); } }
SubClass類與SuperClass類的代碼並無任何修改,只是在main方法裏對SuperClass的靜態字段換了一種調用方式,輸出結果以下:code
由於在main方法中,對SubClass使用了new關鍵字來實例化一個匿名對象,因此此時SubClass是主動引用,所以SubClass會被初始化。對象
被動引用之二:blog
public class SuperClass { static { System.out.println("This is super class"); } public static final int value = 12; } public class SubClass extends SuperClass{ static { System.out.println("This is sub class"); } } public class Main { public static void main(String[] args) { System.out.println(SubClass.value); } }
此時,輸出結果爲:
能夠看到,除了輸出用final修飾的靜態變量,其餘靜態代碼塊裏面的內容是沒有輸出的,證實這兩個類此時並無進行初始化。
這是由於,在編譯階段,經過常量傳播優化,已經將此常量的值「12」存儲到了主類的常量池之中,之後每一次在主類對此常量進行引用,實際上都被轉化爲對主類自身常量池的引用了。便是說,在編譯階段完成以後,主類中並無對SuperClass類的符號引用入口,實際上,在兩個類編譯成Class以後,它們之間已經沒有了聯繫。
被動引用之三:
public class SuperClass { static { System.out.println("This is super class"); } public static int value = 12; } public class SubClass extends SuperClass{ static { System.out.println("This is sub class"); } } public class Main { public static void main(String[] args) { SuperClass[] sc = new SuperClass[10]; } }
執行結果以下:
程序中並無輸出靜態代碼塊中的內容,也沒有輸出靜態字段,說明並無觸發類的初始化階段(具體緣由我似懂非懂,還不能作出合理的解釋)。
類加載的過程
類的加載包括加載、驗證、準備、解析、初始化這5個步驟在加載階段,虛擬機須要完成這三件事:1)經過一個類的全限定名獲取此類的二進制字節流2)將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構3)在內存中生成一個表明此類的java.lang.Class對象,做爲方法區這個類各類數據的訪問入口驗證:1)文件格式驗證:字節流是否符合Class文件的格式規範,而且能被當前的虛擬機處理,該驗證階段主要是保證字節流能正確地解析並存儲於方法區之間;2)元數據驗證:對字節碼描述的語義進行分析,以保證其描述的信息符合Java的語義規範。此驗證階段的目的主要是對類的元數據信息進行語義校驗,保證不存在不符合Java語義規範的元數據信息;3)字節碼驗證:主要目的是經過數據量和控制流分析,確保程序語義是合法的、符合邏輯的4)符號引用驗證:主要目的是確保解析動做能正常執行,若是沒法經過符號引用驗證,那麼將會拋出一個java.lang.IncompatibleClassChangeError異常的子類。準備:這個階段是爲類變量分配內存並設置初始值。類變量是存儲在方法區之中的,而不是想普通的成員變量同樣存儲在java堆之中。準備階段設置的初始值,一般都是零值或者null之類的值。好比說,上述代碼中的public static int value = 12;這一條語句,在準備階段,value被設置的值只是0,到了初始化階段纔會被初始化爲12.固然這種是「一般狀況」,當一個類變量帶有ConstantValue屬性(即用final修飾時),那麼該類變量在準備階段就會初始化爲ConstantValue所指定的值解析:此階段是將常量池中的符號引用替換爲直接引用。其中,符號引用是指,用一組符號來描述所引用的目標,符號能夠是任何形式的字面量,只要使用時能無歧義的定位到目標便可。符號引用與虛擬機實現的內存佈局無關,所引用的目標也並不必定已經加載到內存之中。而直接引用是指,可以直接指向目標的指針、相對偏移量或者能間接定位到目標的句柄。直接引用和虛擬機實現的內存佈局有關,所引用的目標已經加載到內存之中。初始化:初始化階段是執行類構造器<clinit>()的過程,在此階段,類變量的值會被初始化成指定的值而非默認值。