JVM核心之JVM執行和類載入全過程

AskHarries發表於2018-04-23

為什麼研究類載入全過程?

  • 有助於連線JVM執行過程
  • 更深入瞭解java動態性(解熱部署,動態載入),提高程式的靈活性

類載入機制

  • JVM把class檔案載入到記憶體,並對資料進行校驗、解析和初始化,最終形成JVM可以直接使用的java型別的全過程。

  • 載入
    • 將class檔案位元組碼內容載入到記憶體中,並將這些靜態資料轉換成方法區中的執行時資料結構,在堆中生成一個代表這個類的java.lang.Class物件,作為方法區類資料的訪問入口,這個過程需要類載入器參與。

  • 連結 將java類的二進位制程式碼合併到JVM的執行狀態之中的過程
    •   驗證:確保載入的類資訊符合JVM規範,沒有安全方面的問題
    •   準備:正式為類變數(static變數)分配記憶體並設定類變數初始值的階段,這些記憶體都將在方法去中進行分配
    •   解析:虛擬機器常量池的符號引用替換為位元組引用過程
  • 初始化
    • 初始化階段是執行類構造器<clinit>()方法的過程。類構造器<clinit>()方法是由編譯器自動收藏類中的所有類變數的賦值動作和靜態語句塊(static塊)中的語句合併產生
    • 當初始化一個類的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類的初始化
    • 虛擬機器會保證一個類的<clinit>()方法在多執行緒環境中被正確加鎖和同步
    • 當範圍一個Java類的靜態域時,只有真正聲名這個域的類才會被初始化

例1:

public class Demo01 {
    public static void main(String[] args) {
        A a = new A();
        System.out.println(a.width);
    }
}

class A{
    public static int width=100; //靜態變數,靜態域 field
    static{
        System.out.println("靜態初始化類A");
        width = 300 ;
    }
    public A() {
        System.out.println("建立A類的物件");
    }
}複製程式碼

分析:

說明:

記憶體中存在棧、堆(放建立好的物件)、方法區(實際也是一種特殊堆)

1、JVM載入Demo01時候,首先在方法區中形成Demo01類對應靜態資料(類變數、類方法、程式碼…),同時在堆裡面也會形成java.lang.Class物件(反射物件),代表Demo01類,通過物件可以訪問到類二進位制結構。然後載入變數A類資訊,同時也會在堆裡面形成a物件,代表A類。

2、main方法執行時會在棧裡面形成main方法棧幀,一個方法對應一個棧幀。如果main方法呼叫了別的方法,會在棧裡面挨個往裡壓,main方法裡面有個區域性變數A型別的a,一開始a值為null,通過new呼叫類A的構造器,棧裡面生成A()方法同時堆裡面生成A物件,然後把A物件地址付給棧中的a,此時a擁有A物件地址。

3、當呼叫A.width時,呼叫方法區資料。

當類被引用的載入,類只會載入一次

  • 類的主動引用(一定會發生類的初始化)
    • new一個類的物件
    • 呼叫類的靜態成員(除了final常量)和靜態方法
    • 使用java.lang.reflect包的方法對類進行反射呼叫
    • 當虛擬機器啟動,java Demo01,則一定會初始化Demo01類,說白了就是先啟動main方法所在的類
    • 當初始化一個類,如果其父類沒有被初始化,則先初始化它父類
  • 類的被動引用(不會發生類的初始化)
    • 當訪問一個靜態域時,只有真正聲名這個域的類才會被初始化
      • 通過子類引用父類的靜態變數,不會導致子類初始化
    • 通過陣列定義類的引用,不會觸發此類初始化
    • 引用常量不會觸發此類的初始化(常量在編譯階段就存入呼叫類的常量池中了)

例2:

public class Demo01 {
    static{
        System.out.println("靜態初始化Demo01");
    }
    
    
    public static void main(String[] args) throws Exception {
        System.out.println("Demo01的main方法!");
        System.out.println(System.getProperty("java.class.path"));
        
        //主動引用
//        new A();
//        System.out.println(A.width);
//        Class.forName("com.sinosoft.test.A");
        
        
        //被動引用
//        System.out.println(A.MAX);
//        A[] as = new A[10];
        System.out.println(B.width);//B類不會被載入
        
    }
}

class B  extends A {
    static {
        System.out.println("靜態初始化B");
    }
}

class A extends A_Father {
    public static int width=100;   //靜態變數,靜態域    field
    public static final  int MAX=100; 
    
    static {
        System.out.println("靜態初始化類A");
        width=300;
    }
    public A(){
        System.out.println("建立A類的物件");
    }
}

class A_Father extends Object {
    static {
        System.out.println("靜態初始化A_Father");
    }
}複製程式碼

JVM核心之JVM執行和類載入全過程


相關文章