再深入一點:工作學習兩不誤——JVM核心機制講解

類加載過程

JVM 把 class 文件加載到內存,並對數據進行校驗,解析和初始化,最終形成 JVM 可以直接使用的 JAVA 類型的過程。

再深入一點:工作學習兩不誤——JVM核心機制講解

一、將Java類的二進制代碼合併到JVM運行狀態之中的過程

  • 驗證:確保加載的類信息符合JVM規範,沒有安全方面的問題。
  • 準備:正式為類變量 static 變量分配內存並設置類變量初始值的階段,這些內存都將在方法區中進行分配。
  • 解析:虛擬機常量池內的符號引用替換為直接引用的過程。
<code>所謂常量池的符號引用 例如創建一個新的類:
  public class MyClass {
    public static void main(String[] args) {
        String str = "abv";
        int i = 5;
    }
}
這裡面的 `MyClass` `str`  `i` 都可以說是常量 存放於常量池中/<code>

二、初始化

  • 初始化階段是執行類構造器clinit()方法的過程,類構造器clinit()方法是由編譯器自動收集類中的所有類變量的賦值動作和靜態語句塊static塊中的語句併合併產生的。
<code>clinit() 方法平時我們是看不到的,而且也不能自己去定義它/<code>
  • 當初始化一個類的時候,如果發現其父類還沒有進行初始化、則需要先出發其父類的初始化。
  • 虛擬機會保證一個類的 clinit() 方法在多線程環境中被正確的加鎖和同步。
<code>當一個類被初始化的時候肯定是線程安全的/<code>
  • 當訪問一個 Java 類靜態域的時候,只有真正聲明這個域的類才會被初始化。

例圖

再深入一點:工作學習兩不誤——JVM核心機制講解

<code>/**
* Created by BF on 2017/9/14.
* 瞭解JVM加載類全過程
*/
public class demo01 {
    public static void main(String[] args) {
        // 當A對象被創建的時候 先會執行靜態代碼塊,再實執行A的構造方法
        A a = new A();
        System.out.println(a.width);
        // 輸出順序為  創建初始化類A--> width = 300 --> 創建A對象
    }
}
class A {
    public static int width = 100;
    static {
        System.out.println("靜態初始化類A");
        width = 300;
    }
    public A(){
        System.out.println("創建A對象");
    }
}/<code>

三、加載

  • 將 class 文件字節碼內容加載到內存中,並將這些靜態數據轉換成方法 區中的運行時數據結構,在堆中生成一個代表這個類的 java.lang.Class 對象,作為方法區類數據的訪問入口,這個過程需要類的加載器參與。
  • 類加載全過程 -->重點理解

便於理解,先把代碼貼出來

<code>public class demo01 {
    public static void main(String[] args) throws ClassNotFoundException {
        // 主動引用
        new A();
        System.out.println(A.width);
        Class.forName("com.wiceflow.JVM.A");
        // 被動引用
        System.out.println(A.MAX);
        A[] as = new A[10];
        System.out.println(B.width);
    }
}
class A extends A_Father{
    public static int width = 100;
    public static final int MAX = 200;
    static {
        System.out.println("靜態初始化類A");
        width = 300;
    }
    public A(){
        System.out.println("創建A對象");
    }
}
class A_Father{
    static {
        System.out.println("靜態初始化A的父類");
    }
}
class B extends A{
    static {
        System.out.println("靜態初始化類B");
    }
}/<code>

new A() 正常打印結果:

<code>  靜態初始化A的父類
  靜態初始化類A
  創建A對象/<code>

類的主動引用(一定會發生類的初始化)

  • new 一個類的對象:當new一個類的新對象,類必然會初始化 eg:new A()
  • 調用類的靜態成員(除了final常量)和靜態方法:eg:上述類A中有靜態成員 width 當在其他類中調用到 A.width,類A一定會初始化
  • 使用java.lang.reflect包的方法對類進行反射調用:放射調用該類必會導致該類初始化,否則反射調用不會成功 eg: Class.forName("com.wiceflow.JVM.A")
  • 當虛擬機啟動,java Hello,則一定會初始化Hello類,說白了就是啟用Main方法所在的類
  • 當初始化一個類,如果其父類沒有被初始化,則會先初始化其父類:由上述代碼可以看出 A類繼承A_Father類,當A類初始化的時候,因為其繼承了A_Father,所以會先初始化A_Father類,而每個類都會繼承Object類,所以這個類一定會被初始化
再深入一點:工作學習兩不誤——JVM核心機制講解

類的被動引用(不會發生類的初始化)

  • 當訪問一個靜態域時,只有真正聲明這個域的類才會被初始化:
<code>通過子類引用父類的靜態變量,不會導致子類初始化/<code>

例如上面類B繼承了類A,當B中調用B.width的時候,由於類B本身沒有width變量,所以是取自其父類A,這時候虛擬機初始化的是類A,而類B並不會被初始化

  • 引用常量不會觸發此類的初始化(常量在編譯階段就存入調用類的常量池中了):eg:在A中定義了一個final常量MAX,這個常量在編譯的時候就會創建並存儲在方法區(特殊的堆)中,這時候調用只是在方法區將其取出,並不會涉及類A的初始化

推薦閱讀:

RocketMQ全貌解析,阿里不愧是阿里

為什麼大廠的面試題問的都是底層原理,前阿里P7架構師是這樣說的

java開發必知必會的技能,沒有系統掌握Kafka,你就缺少核心競爭力


分享到:


相關文章: