類從被加載到虛擬機內存中開始,到卸載出內存爲止,它的整個生命週期包括:加載、驗證、準備、解析、初始化、使用和卸載七個階段。其中驗證、準備、解析3個部分統稱爲鏈接。類加載的過程包括了加載、驗證、準備、解析、初始化五個階段。編程
加載、驗證、準備、初始化和卸載這5個階段的順序時肯定的,類的加載過程必須按照這種順序循序漸進的開始,而解析階段則不必定,它在某些狀況下能夠在初始化階段以後開始,這是爲了支持Java語言的運行時綁定(也成爲動態綁定或晚期綁定)。另外注意這裏的幾個階段是按順序開始,而不是按順序進行或完成,由於這些階段一般都是互相交叉地混合進行的,一般在一個階段執行的過程當中調用或激活另外一個階段。數組
類初始化是類加載過程的最後一個階段,到初始化階段,才真正開始執行類中的Java程序代碼。虛擬機規範嚴格規定了有且只有5種狀況必須當即對類進行初始化:post
- 第一種:遇到new、getstatic、putstatic、invokestatic這四條字節碼指令時,若是類尚未進行過初始化,則須要先觸發其初始化。生成這四條指令最多見的Java代碼場景是:使用new關鍵字實例化對象時、讀取或設置一個類的靜態字段(static)時(被static修飾又被final修飾的,已在編譯期把結果放入常量池的靜態字段除外)、以及調用一個類的靜態方法時。
- 第二種:使用Java.lang.refect包的方法對類進行反射調用時,若是類尚未進行過初始化,則須要先觸發其初始化。
- 第三種:當初始化一個類的時候,若是發現其父類尚未進行初始化,則須要先觸發其父類的初始化。
- 第四種:當虛擬機啓動時,用戶須要指定一個要執行的主類,虛擬機會先執行該主類。
- 第五種:當使用JDK1.5支持時,若是一個java.langl.incoke.MethodHandle實例最後的解析結果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,而且這個方法句柄所對應的類沒有進行過初始化,則須要先觸發其初始化。
虛擬機規定有且只有這5種狀況纔會觸發類的初始化,這5中場景中的行爲稱爲對一個類進行主動引用,除此以外全部引用類的方式都不會觸發其初始化,稱爲被動引用。下面舉一些例子來講明被動引用。url
一、經過子類引用父類中的靜態字段,這時對子類的引用爲被動引用,所以不會初始化子類,只會初始化父類
package org.wrh.classupload;
/* * 經過子類引用父類的靜態字段,不會致使子類初始化 * */ public class TestClassDemo01 { public static void main(String[] args) { // TODO Auto-generated method stub System.out.println(SubClass.i);//子類引用父類的靜態字段 } } class SuperClass{ public static int i=3;//父類的static字段 static{//當此類在虛擬機中初始化的時候,此static塊將會被執行 System.out.println("SuperClass init"); } } class SubClass extends SuperClass{ static{//當此類在虛擬機中初始化的時候,此static塊將會被執行 System.out.println("SubClass init"); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
程序運行結果以下:spa
SuperClass init
3翻譯
即只輸出了「SuperClass init」,而沒有輸出「SubClass init」。code
結論:對於靜態字段,只有直接定義這個字段的類纔會被初始化,所以經過其子類來引用父類中定義的靜態字段,只會觸發父類的初始化而不會觸發子類的初始化。至因而否要觸發子類的加載和驗證,在虛擬機規範中並未明確規定,這點取決於虛擬機的具體實現。htm
使用new實例化對象時,會先把父類初始化,而後再初始化此類自己
package org.wrh.classupload;
/* * 經過子類引用父類的靜態字段,不會致使子類初始化 * */ public class TestClassDemo01 { public static void main(String[] args) { //System.out.println(SubClass.i); //SuperClass s=new SuperClass(); SubClass s=new SubClass(); } } class SuperClass{ public static int i=3; static{ System.out.println("SuperClass init"); } } class SubClass extends SuperClass{ static{ System.out.println("SubClass init"); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
運行結果以下:
SuperClass init
SubClass init
經過數組定義來引用類,不會觸發此類的初始化
package org.wrh.classupload;
public class TestClassDemo02 { public static void main(String[] args) { // TODO Auto-generated method stub SuperClass_1 superClass[]=new SuperClass_1[5]; } } class SuperClass_1{ static{ System.out.println("SuperClass init"); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
執行後沒有輸出任意內容,說明沒有進行任何類的初始化工做。
可是,但這段代碼裏觸發了另外一個名爲「LLSuperClass_1」的類的初始化,它是一個由虛擬機自動生成的、直接繼承於java.lang.Object的子類,建立動做由字節碼指令newarray觸發,很明顯,這是一個對數組引用類型的初始化。
常量在編譯階段會存入調用它的類的常量池中,本質上沒有直接引用到定義該常量的類,所以不會觸發定義常量的類的初始化
package org.wrh.classupload;
public class NotInitialization { public static void main(String[] args) { System.out.println(ConstClass.VALUE); } } class ConstClass{ public static final int VALUE=3; static{ System.out.println("ConstClass init"); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
運行結果以下:
3
沒有輸出「ConstClass init」,所以能夠獲得當咱們引用final修飾的常量時此類並無初始化。
雖然程序中引用了ConstClass類的常量VALUE,可是在編譯階段將此常量的值「3」存儲到了調用它的類ConstClass的常量池中,對常量Const.VALUE的引用實際上轉化爲了ConstClass類對自身常量池的引用。也就是說,實際上ConstClass的Class文件之中並無Const類的符號引用入口,這兩個類在編譯成Class文件後就不存在任何聯繫了。
接口的加載過程
接口的加載過程與類加載過程稍微有點不一樣,針對接口須要作一些特殊的說明:接口也有初始化過程,這點與類是一致的,上面的代碼都是用靜態語句塊」static{}「來輸出初始化信息的,而接口中不能使用」static{}「語句塊,可是編譯器仍然會爲接口生成」()」類構造器,用於初始化接口中所定義的成員變量。接口與類真正有所區別的是前面講述的5種「有且只有」須要開始初始化場景中的第三種:當一個類在初始化時,要求其父類所有都已經初始化過了,可是在接口在初始化的時候,並不要求其父接口都完成了初始化,只要在真正使用到父接口的時候(如引用接口中定義的常量)纔會初始化。