1. 程式人生 > >深入理解java虛擬機器(七)類載入的時機

深入理解java虛擬機器(七)類載入的時機

Class 檔案中描述的各種資訊都必須載入到虛擬機器中才能執行和使用。而虛擬機器怎麼載入這些Class 檔案呢?Class 檔案進入到虛擬機器中會發生什麼變化呢?

虛擬機器類載入機制是指 虛擬機器把描述類的資料從 Class 檔案載入到記憶體中,並對資料進行校驗、轉換解析、初始化,最終形成可以被虛擬機器直接使用的Java 型別。

與那些在編譯器進行連線工作的語言不同,Java 中,類的載入一直到初始化過程都是在執行期間完成的,雖然會損失一點效能,但是卻使Java 應用程式具有高度靈活的特性。Java 可以動態擴充套件的特性就是依賴於執行期動態載入和動態連線這個特點實現的。

類載入的時機

類從被載入到虛擬機器記憶體開始,到卸載出記憶體為止,它的整個生命週期包括:載入

(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和解除安裝(Unloading)7個階段。其中,驗證、準備和解析統稱為連線(Linking)。過程如下圖所示。


圖1 類的生命週期

上述七個過程,除了解析(Resoluton)階段外,其餘的六個過程都必須按部就班的“開始”,之間可能有部分相互交叉的混合執行。 解析過程在某些情況下,可以在初始化階段之後再開始,這也是為了支援Java 語言的執行時繫結(也稱為動態繫結或晚期繫結)。

Java虛擬機器規範並沒有規定什麼時候需要進行類的載入階段,但是卻規定5中情況必須對類進行初始化。

  1. 例項化物件、讀寫類靜態欄位、呼叫靜態方法的時候
  2. 使用反射呼叫的時候
  3. 初始化類時,需要先觸發父類的初始化
  4. 虛擬機器啟動時,會先初始化包含 main() 方法的主類
  5. 使用JDK1.7 的動態語言支援的時候,如果 java.lang.invoke.MethodHandle 例項最後的解析結果REF_getStatic 、REF_putStatic、REF_invokeStatic的方法控制代碼,控制代碼對應的類會被初始化
Java虛擬機器規範規定有且只有這五種場景中會觸發類的初始化,這種行為成為對一個類進行主動引用。除此之外,所有引用類的方式都不會觸發初始化,稱為被動引用

被動引用
  1. 通過子類引用父類中定義的靜態欄位,只會觸發父類的初始化。至於是否會觸發子類的載入和驗證,取決於虛擬機器的具體實現(HotSpot不會載入)。
  2. 通過陣列定義來引用類,如 A[] ints = new A[10] ,  不會觸發A 類的初始化。而是會會觸發名為 LA的類初始化。它是一個由虛擬機器自動生成的、直接繼承於Object 的子類,建立動作由位元組碼指令 newarray 觸發。這個類代表了一個元素型別為 A 的一位陣列,陣列中的屬性和方法都實現在這個類中。Java 語言中陣列的訪問比C/C++ 安全是因為這個類封裝了陣列元素的訪問方法。
  3. 常量在編譯階段會存入呼叫類的常量池中,本質上並沒有直接引用到定義常量的類,因此不會觸發定義常量的類的初始化。
package xiaoye.java;
class Constant{
	static {
		System.out.print("ConstantClass inited!");
	}
	public static final String HELLOWORLD = "hehe";
}

public class ConstantClass {
	public static void main(String[] args){
		System.out.print(Constant.HELLOWORLD);
	}

}

介面載入的時機

介面也有初始化過程,但是介面中不能使用static 語句塊,但是編譯器仍然會為介面生成 <clinit> 類構造器初始化介面中定義的成員變數。 介面與類真正的區別是初始化場景中的第三種情況:當一類在初始化的時候,會要求其所有的父類都初始化完成,但是介面在初始化的時候,並不要求其父介面全部完成了初始化,只有在真正用到了父介面的時候(如引用介面中定義的常量)才會初始化。