虛擬機器載入機制——類載入時機
阿新 • • 發佈:2018-11-11
文章目錄
在閱讀本文之前需要注意兩點。第一,本文指的“類”包括類和介面,對於類和介面的不同之處,會特別指明。第二,本文指定“Class檔案”並非存在磁碟上,這裡說的“Class檔案”是指一串二進位制的位元組流
一、類載入(初始化)的時機
類從被虛擬機器載入到記憶體中到從記憶體中解除安裝,由以下幾個過程(生命週期),如下圖所示:
其實第一個類載入的時機,java虛擬機器規範並沒有進行強行約束,而是有虛擬機器的具體實現來自動把握的。然而類的初始化,虛擬機器規範就做出了規定了。
類的初始化有以下5個時機:
- 使用new例項化物件時、訪問修改類的靜態欄位(用final修飾的靜態欄位除外,因為它在編譯時已經放入了常量池了,不存在類的符號引用了)。在位元組碼層面就是上就是遇到new、puststatic、getstatic、invokesatic這幾個位元組碼指令時,如果類沒有被初始化,就進行類的初始化。
- 如果對類進行反射呼叫時,如果沒有進行類的初始化,則需要先觸發其初始化。
- 當初始化一個類時,發現其父類沒有被初始化,就先觸發其父類的初始化。
- 當虛擬機器啟動時,使用者需要指定一個要執行的主類(包含main方法那個類),虛擬機器先初始化這個類。
- 當使用JDK1.7的動態語言支援時,如果一個java.lang.invoke.MethodHandle例項的最終的解析結果REF_getStatic、REF_putStatic、REF_invokeStatic的方法控制代碼,並且這個方法控制代碼所對應的類沒有被進行過初始化,則需要先對其進行初始化。
二、主動引用、被動引用及被動引用的幾個例子
上面的5個時機稱為對類的主動引用(需要對類進行初始化的)。除此之外,所有引用類的方式都不會觸發類的初始化,被稱為類的被動引用。下面是類的三個類的被動引用的例子:
2.1 通過子類引用父類的靜態欄位,不會導致子類的初始化
程式碼:
public class Main {
public static void main(String[] args) {
//呼叫父類的靜態成員,並不會導致子類的初始化,只會進行父類的初始化
int b = subClass.a;
}
}
class superClass {
static {
System.out.println("superClass進行初始化");
}
public static int a = 3;
}
class subClass extends superClass {
static {
System.out.println("subClass進行初始化");
}
}
執行結果:
上圖顯示了,沒有對子類進行初始化。
2.2 通過陣列定義引入類,不會觸發類的初始化
程式碼:
public class Main {
public static void main(String[] args) {
//通過陣列定義引入類,不會觸發類的初始化
subClass[] subArray=new subClass[10];
}
}
class superClass {
static {
System.out.println("superClass進行初始化");
}
public static int a = 3;
}
class subClass extends superClass {
static {
System.out.println("subClass進行初始化");
}
}
執行結果:
執行結果就是沒輸出。
2.3 訪問用final修飾的靜態欄位,不會觸發類的初始化
程式碼:
public class Main {
public static void main(String[] args) {
//訪問用final修飾的靜態欄位,不會觸發類的初始化
int b = superClass.a;
}
}
class superClass {
static {
System.out.println("superClass進行初始化");
}
public final static int a = 3;
}
class subClass extends superClass {
static {
System.out.println("subClass進行初始化");
}
}
執行結果:
執行,沒有輸出。常量在編譯階段會存入呼叫類(Main)的常量池中,本質上並沒有直接引用到定義常量的類(superClass),因此就不會觸發定義常量的類的初始化。
三、類載入的時機中類和介面的區別
類載入的時機中,類和介面的區別在於上面類初始化的時機中的第三點。當一個類初始化時,需要其全部父類都被進行初始。然而介面並不需要父介面全部完成初始化,只有在真正是要到父介面時(如引用介面中定義的常量)才會初始化父介面。
下面程式碼意義不大。
public class Main {
public static void main(String[] args) {
//初始化子類時,需要先初始化其父類介面
int b = subClass.a;
new subInterface(){
@Override
public void subMethod() {
System.out.println(subInterface.b);
}
@Override
public void superMethod() {
System.out.println(subInterface.a);
}
}.subMethod();
}
}
interface superInterface {
public final static int a = 3;
void superMethod();
}
interface subInterface extends superInterface {
public final static int b = 3;
void subMethod();
}
class superClass {
static {
System.out.println("初始化父類");
}
}
class subClass extends superClass{
static {
System.out.println("初始化子類");
}
public static int a=3;
}
執行結果(怎樣看介面是否進行初始化,不知道):