本文根據《深刻理解java虛擬機》第7章部份內容整理 java
Java虛擬機把描述類的數據從Class文件加載到內存,並對數據進行校驗、轉換解析和初始化,最終造成能夠被虛擬機直接使用的Java類型,這就是虛擬機的加載機制。 數組
類從被加載到虛擬機內存中開始,到卸載出內存爲止,它的整個生命週期包括了:加載(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(using)、和卸載(Unloading)七個階段。其中驗證、準備和解析三個部分統稱爲鏈接(Linking),這七個階段的發生順序以下圖所示: spa
如上圖所示,加載、驗證、準備、初始化和卸載這五個階段的順序是肯定的,類的加載過程必須按照這個順序來循序漸進地開始,而解析階段則不必定,它在某些狀況下能夠在初始化階段後再開始。 對象
類的生命週期的每個階段一般都是互相交叉混合式進行的,一般會在一個階段執行的過程當中調用或激活另一個階段。 接口
Java虛擬機規範沒有強制性約束在何時開始類加載過程,可是對於初始化階段,虛擬機規範則嚴格規定了有且只有四種狀況必需當即對類進行「初始化」(而加載、驗證、準備階段則必需在此以前開始),這四種狀況歸類以下: 生命週期
1.遇到new、getstatic、putstatic或invokestatic這4條字節碼指令時,若是類沒有進行過初始化,則須要先觸發其初始化。生成這4條指令最多見的Java代碼場景是:使用new關鍵字實例化對象時、讀取或者設置一個類的靜態字段(被final修飾、已在編譯器把結果放入常量池的靜態字段除外)時、以及調用一個類的靜態方法的時候。 圖片
2.使用java.lang.reflect包的方法對類進行反射調用的時候,若是類沒有進行過初始化,則須要先觸發其初始化。 內存
3.當初始化一個類的時候,若是發現其父類尚未進行過初始化,則須要觸發父類的初始化。 ssl
4.當虛擬機啓動時,用戶須要指定一個執行的主類(包含main()方法的類),虛擬機會先初始化這個類。 get
對於這四種觸發類進行初始化的場景,在java虛擬機規範中限定了「有且只有」這四種場景會觸發。這四種場景的行爲稱爲對類的主動引用,除此之外的全部引用類的方式都不會觸發類的初始化,稱爲被動引用。
下面經過三個實例來講明被動引用:
示例一
父類SuperClass.java
子類SubClass.java
主類NotInitialization.java
輸出結果:
由結果能夠看出只輸出了「SuperClass init!」,沒有輸出「SubClass init!」。這是由於對於靜態字段,只有直接定義該字段的類纔會被初始化,所以當咱們經過子類來引用父類中定義的靜態字段時,只會觸發父類的初始化,而不會觸發子類的初始化。
示例二
父類SuperClass.java如上一個示例同樣
主類NotInitialization.java
輸出結果爲空
沒有輸出「SuperClass init!」說明沒有觸發類com.chenzhou.classloading.SuperClass的初始化階段,可是這段代碼會觸發「[Lcom.chenzhou.classloading.SuperClass」類的初始化階段。這個類是由虛擬機自動生成的,該建立動做由newarray觸發。
示例三
常量類ConstClass.java
主類NotInitialization.java
輸出:hello world
上面的示例代碼運行後也沒有輸出「SuperClass init!」,這是由於雖然在Java源碼中引用了ConstClass類中的常量HELLOWORLD,可是在編譯階段將此常量的值「hello world」存儲到了NotInitialization類的常量池中,對於常量ConstClass.HELLOWORLD的引用實際上都被轉化爲NotInitialization類對自身常量池的引用了。實際上NotInitialization的Class文件之中已經不存在ConstClass類的符號引用入口了。
接口的加載過程與類加載的區別在於上面提到的四種場景中的第三種,當類在初始化時要求其父類都已經初始化過了,可是一個接口在初始化時,並不要求其父類都完成了初始化,只有在真正用到父類接口的時候(如引用父接口的常量)纔會初始化。