jvm_類加載

在Java代碼中,類型的加載、鏈接和初始化過程都是在程序運行期間完成的。

  • 加載:查找並加載類的二進制數據;
  • 鏈接:
    • 驗證:確保被加載的類的正確性;
    • 準備:爲類的靜態變量分配內存,並將其初始化爲默認值;
    • 解析:把類中的符號引用轉換爲直接引用
  • 初始化:爲類的靜態變量賦予正確的初始值;
public class test {
    public static int a = 1;
    ...
}

例如上面的代碼,是先將 0 賦值給 a,而後在初始化的時候將 1 賦值給 a;java

類的加載:

將類的 .class 文件二進制數據讀入到內存中,將其放置在運行時數據區的方法區內,而後在內存中建立一個java.lang.Class對象,用來封裝類在方法區內的數據結構;
加載方式:數據庫

  • 從本地系統中直接加載;
  • 經過網絡下載.class文件
  • 從.zip,.jar 等歸檔文件中加載;
  • 從專有數據庫中提取.class文件;
  • 將 Java 源文件動態的編譯爲.class文件

Java虛擬機結束生命週期:

  • 執行了 System.exit() 方法;
  • 程序正常執行結束;
  • 程序在運行過程當中遇到了異常或錯誤而異常終止;
  • 因爲操做系統出現錯誤而致使Java虛擬機進程終止;

Java程序對類的使用方式有兩種

  • 主動使用(七種)
    • 建立類的實例
    • 訪問某個類或接口的靜態變量/靜態方法,或者對該靜態變量進行賦值;(getstatic, putstatic)
    • 調用類的靜態方法;(invokstatic)
    • 反射(如Class.forName("com.test.Test"));
    • 初始化一個類的子類;
    • Java虛擬機啓動時,被標註爲啓動類的類;
    • JDK1.7開始提供對動態語言的支持:java.lang.invoke.MethodHandle 實例的解析結果REF_getStatic,REF_putStatic,REF_invokeStatic句柄對應的類沒有初始化,則初始化;
  • 被動使用:除上面的七種方式,其餘使用Java類的方式都被看做是對類的被動使用,都不會致使類的初始化;
    全部的Java虛擬機實現必須在每一個類或接口被Java程序「首次主動使用」時纔會初始化他們;

例子1:父類

/**
 * 1. 對於靜態字段來講,只有直接定義了該字段的類纔會被初始化
 * 2. 當一個類初始化時,要求其父類已經所有初始化完成
 * +XX:TraceClassLoading:用於追蹤類的加載信息並打印出來
 */
public class MyTest1 {
    public static void main(String[] args) {
        /**
         * MyParent1 static block
         * hello world
         */
        System.out.println(MyChild1.str);
        /**
         * MyParent1 static block
         * MyChild1 static block
         * welcome
         */
        // System.out.println(MyChild1.str2);
    }
}
class MyParent1 {
    public static String str = "hello world";
    static {
        System.out.println("MyParent1 static block");
    }
}
class MyChild1 extends MyParent1 {
    public static String str2 = "welcome";
    static {
        System.out.println("MyChild1 static block");
    }
}

控制檯輸出信息網絡

[Loaded com.godfunc.jvm.classloader.MyParent1 from file:/Users/godfunc/WorkSpace/IdeaSpace/learn-java/jvm/build/classes/java/main/]
[Loaded com.godfunc.jvm.classloader.MyChild1 from file:/Users/godfunc/WorkSpace/IdeaSpace/learn-java/jvm/build/classes/java/main/]
MyParent1 static block
hello world

JVM規範容許類加載器在預料某個類將要被使用時就預先加載它,若是在預先加載中遇到了.class文件缺失或者存在錯誤,類加載器必須在程序「首次主動使用」該類時才報告錯誤(LinkageError錯誤),若是這個類一直沒有被主動使用,那麼類加載器就不會報告這個錯誤。數據結構

例2:常量

/**
 * 常量在編譯階段會被存入到調用這個常量的方法所在的類的常量池當中,本質上,調用類並無直接引用到定義常量的類,
 * 所以並不會觸發定義常量的類的初始化
 * 注意:這裏的指的是將常量放到了MyTest2的常量池中,以後MyTest2和MyParent2就沒有任何關係了
 * 甚至能夠刪除MyParent2的class文件
 * 助記符:
 * ldc表示將int, float, 或者String的常量值從常量池推送至棧頂。
 * bipush表示將單字節(-128 ~ 127)的常量推送至至棧頂
 * sipush表示將將一個整型(-32768 ~ 32767)常量值推送至棧頂
 * iconst_1表示將int型的1推送至棧頂。-1到5 (iconst_m1 ~ iconst_5)
 */
public class MyTest2 {
    public static void main(String[] args) {
        System.out.println(MyParent2.str);
    }
}
class MyParent2 {
    public static final String str = "hello world";
    static {
        System.out.println("MyParent2 static block");
    }
}

例3:不肯定值的常量類

/**
 * 當一個常量的值並不是編譯器能夠肯定的,那麼其值就不會被放到調用類的常量池中,
 * 這時程序運行時,會致使主動使用這個常量所在的類,顯然會致使這個類被初始化。
 */
public class MyTest3 {
    public static void main(String[] args) {
        /**
         * MyParent3 static block
         * 59ddfbea-ed41-4ab9-a945-88466d6a5ead
         */
        System.out.println(MyParent3.str);
    }
}
class MyParent3 {
    public static final String str = UUID.randomUUID().toString();
    static {
        System.out.println("MyParent3 static block");
    }
}

例4:類的準備和初始化

/**
 * 在準備階段,counter1=0 counter=0,而後進入初始化階段時counter1初始化爲1,執行new Singleton(),counter1 + 1 = 2,counter2 + 1 = 1
 * 而後初始化counter2,counter2的值被初始化爲0,最後輸出的結果爲 2 0
 */
public class MyTest6 {
    public static void main(String[] args) {
        Singleton signleton = Singleton.getSingleton();
        // 2 1
        System.out.println(Singleton.counter1 + " " + Singleton.counter2);
    }
}
class Singleton {
    public static int counter1 = 1;
    private static Singleton singleton = new Singleton();
    private Singleton() {
        counter1 += 1;
        counter2 += 1;
        System.out.println(counter1);
        System.out.println(counter2);
    }
    public static int counter2 = 0;
    public static Singleton getSingleton() {
        return singleton;
    }
}

例5:接口

/**
 * 當一個類被初始化的時候,它所實現的接口是不會被初始化的
 */
public class MyTest5 {
    public static void main(String[] args) {
        // 6
        System.out.println(MyChild5.b);
    }
}
interface MyParent5 {
    public static Thread thread = new Thread() {
        {
            System.out.println("MyParent5 invoked");
        }
    };
}
class MyChild5 implements MyParent5 {
    public static int b = 6;
}

類的初始化步驟

  • 假如這個類尚未被加載和鏈接,那就先進行加載和鏈接;
  • 假如類存在直接父類,而且這個父類尚未被初始化,那就先初始化直接父類;
  • 假如類中存在初始化語句,那就依次執行這些初始化語句;

類加載器

根類(啓動類)加載器 -> 擴展類加載器 -> 系統類加載器 -> 用戶自定義類加載器dom

相關文章
相關標籤/搜索