1. 程式人生 > >[Java] JVM核心機制

[Java] JVM核心機制

JVM核心機制

類載入過程

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

將Java類的二進位制程式碼合併到JVM執行狀態之中的過程

  • 驗證:確保載入的類資訊符合JVM規範,沒有安全方面的問題。  

  • 準備:正式為類變數 static 變數分配記憶體並設定類變數初始值的階段,這些記憶體都將在方法區中進行分配。

  • 解析:虛擬機器常量池內的符號引用替換為直接引用的過程。
  所謂常量池的符號引用 例如建立一個新的類:
  public class MyClass {
      public static void main(String[] args) {
          String str = "abv";
          int i = 5;
      }
  }
這裡面的 `MyClass` `str`  `i` 都可以說是常量 存放於常量池中

初始化

  • 初始化階段是執行類構造器clinit()方法的過程,類構造器clinit()方法是由編譯器自動收集類中的所有類變數的賦值動作和靜態語句塊static塊中的語句併合併產生的。

    <font color=red>clinit() 方法平時我們是看不到的,而且也不能自己去定義它</font>

  • 當初始化一個類的時候,如果發現其父類還沒有進行初始化、則需要先出發其父類的初始化。
  • 虛擬機器會保證一個類的 clinit() 方法在多執行緒環境中被正確的加鎖和同步。

    當一個類被初始化的時候肯定是執行緒安全的

  • 當訪問一個 Java 類靜態域的時候,只有真正宣告這個域的類才會被初始化。

    例圖

                                              

     

    /**
    * 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物件");
    }
    }

載入

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

  • 類載入全過程  --><font color=gray>重點理解</font>
    便於理解,先把程式碼貼出來

    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");
      }
    }
    new A() 正常列印結果:
      靜態初始化A的父類
      靜態初始化類A
      建立A物件
    • 類的主動引用(一定會發生類的初始化)

      • 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類,所以這個類一定會被初始化

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

      • 當訪問一個靜態域時,只有真正宣告這個域的類才會被初始化

        • <font color=“”>通過子類引用父類的靜態變數,不會導致子類初始化</font>
          >例如上面類B繼承了類A,當B中呼叫B.width的時候,由於類B本身沒有width變數,所以是取自其父類A,這時候虛擬機器初始化的是類A,而類B並不會被初始化
      • 引用常量不會觸發此類的初始化(常量在編譯階段就存入呼叫類的常量池中了)
        >eg:在A中定義了一個final常量MAX,這個常量在編譯的時候就會建立並存儲在方法區(特殊的堆)中,這時候呼叫只是在方法區將其取出,並不會涉及類A的初始化