1. 程式人生 > >虛擬機器載入機制——類載入時機

虛擬機器載入機制——類載入時機

文章目錄


在閱讀本文之前需要注意兩點。第一,本文指的“類”包括類和介面,對於類和介面的不同之處,會特別指明。第二,本文指定“Class檔案”並非存在磁碟上,這裡說的“Class檔案”是指一串二進位制的位元組流

一、類載入(初始化)的時機

類從被虛擬機器載入到記憶體中到從記憶體中解除安裝,由以下幾個過程(生命週期),如下圖所示:
在這裡插入圖片描述

其實第一個類載入的時機,java虛擬機器規範並沒有進行強行約束,而是有虛擬機器的具體實現來自動把握的。然而類的初始化,虛擬機器規範就做出了規定了。

類的初始化有以下5個時機:

  1. 使用new例項化物件時、訪問修改類的靜態欄位(用final修飾的靜態欄位除外,因為它在編譯時已經放入了常量池了,不存在類的符號引用了)。在位元組碼層面就是上就是遇到new、puststatic、getstatic、invokesatic這幾個位元組碼指令時,如果類沒有被初始化,就進行類的初始化。
  2. 如果對類進行反射呼叫時,如果沒有進行類的初始化,則需要先觸發其初始化。
  3. 當初始化一個類時,發現其父類沒有被初始化,就先觸發其父類的初始化。
  4. 當虛擬機器啟動時,使用者需要指定一個要執行的主類(包含main方法那個類),虛擬機器先初始化這個類。
  5. 當使用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;
}

執行結果(怎樣看介面是否進行初始化,不知道):
在這裡插入圖片描述