玩轉Java虛擬機(二)

打卡學習JVM,第二天

本人學習過程中所整理的代碼,源碼地址

- 類加載器的類型

  • Java虛擬機自帶的加載器:根類加載器(Bootstrap),擴展類加載器(Extension),系統類加載器(System)
  • 用戶自定義的類加載器:java.lang.ClassLoader的子類,用戶可以定製類的加載方式
    在這裏插入圖片描述
    Tips:類加載器並不需要等到某個類被“首次主動使用”時再加載它

JVM規範類允許類加載器在預料某個類將要被使用時就預先加載它,如果在預先加載的過程中遇到了.class文件缺失或存在錯誤,類加載器必須在程序首次主動使用該類時才報告錯誤(LinkageError)
如果這個類一直沒有程序被主動使用,那麼類加載器就不會報告錯誤

- 類加載器的雙親委託機制

各個加載器按照父子關係形成了樹形結構,除了根類加載器之外,其餘的類加載器都有且只有一個父加載器;若有一個類加載器能夠成功加載Test類,那麼這個類加載器被稱爲“定義類加載器”,所有能成功返回Class對象引用的類加載器(包括定義類加載器)都被稱爲“初始類加載器”
在這裏插入圖片描述
若有一個類加載器能夠成功加載Test類,那麼這個類加載器就被稱爲定義類加載器,所有能成功返回Class對象引用的類加載器(包括定義類加載器)都被稱爲初始類加載器

//擴展類加載器的父類爲根加載器,一般用null表示
public class MyTest12 {
    public static void main(String[] args) {
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        System.out.println(classLoader);

        while (null!=classLoader){
            classLoader = classLoader.getParent();
            System.out.println(classLoader);
        }
    }
}
/*
sun.misc.Launcher$AppClassLoader@73d16e93
sun.misc.Launcher$ExtClassLoader@15db9742
null
*/

數組類 Class 對象不是由類加載器創建的,而是由 Java 運行時根據需要自動創建。數組類的類加載器由Class.getClassLoader() 返回,該加載器與其元素類型的類加載器是相同的;如果該元素類型是基本類型,則該數組類沒有類加載器。

public class MyTest14 {
    public static void main(String[] args) {
        String[] strings = new String[1];
        System.out.println(strings.getClass().getClassLoader());
        MyTest14[] myTest14s = new MyTest14[2];
        System.out.println(myTest14s.getClass().getClassLoader());
        int[] ints = new int[2];
        System.out.println(ints.getClass().getClassLoader());
    }
}
/*
null     ->啓動類加載器
sun.misc.Launcher$AppClassLoader@73d16e93
null	 ->沒有類加載器
*/

- 雙親委託機制的優點

  • 提高軟件系統的安全性——用戶自定義的類加載器不可能加載應該由父加載器加載的可靠類,從而防止不可靠代碼代替由父加載器加載的可靠代碼。例如String類總是由跟類加載器加載

- 類的初始化步驟

  • 加入這個類還沒有被加載和連接,那就先進行加載和連接
  • 加入類存在直接父類,並且這個父類還沒有被初始化,那就先初始化直接父類
  • 加入類中存在初始化語句,那就依次執行這些初始化語句

- 類的初始化時機

當Java虛擬機初始化一個類時,要求它的所有父類都已經被初始化,但是這條規則並不適用於接口

  • 在初始化一個類時,並不會先初始化它所實現的接口
  • 在初始化一個接口時,並不會先初始化它的父接口
    因此一個父接口不會因爲它的子接口或者實現類的初始化而初始化。只有當程序首次使用特定接口的靜態變量時纔會導致該接口的初始化
public class MyTest {
    public static void main(String[] args) {
        System.out.println(MyChild1.b);
    }
}
interface MyParent1 {
    public static Thread thread = new Thread(){
    	{
			System.out.println("MyParent1 invoked");
		}
    };
}
class MyChild1 implements MyParent1 {
    public static int b = 5;
}
/*5*/
//調用ClassLoader類的loadClass方法加載一個類,並不是對類的主動使用,因此不會導致類的初始化
class CL{
    static {
        System.out.println("Class CL");
    }
}
public class MyTest11 {
    public static void main(String[] args) throws ClassNotFoundException {
        ClassLoader loader = ClassLoader.getSystemClassLoader();

        Class<?> clazz = loader.loadClass("classloader.CL");
        System.out.println(clazz);
        System.out.println("----------");
        clazz = Class.forName("classloader.CL");
        System.out.println(clazz);
    }
}
/*
class classloader.CL
----------
Class CL
class classloader.CL
*/

通過前面所有代碼的驗證,基本可以確定類的主動使用會導致類的初始化,因此類的初始化時機總結如下,總共爲7中情況

  • 創建類的實例
  • 訪問某個類或接口的靜態變量,或者對該靜態變量賦值
  • 調用類的靜態方法
  • 反射(如Class.forName(“類的全限定名”))
  • 初始化一個類的子類
  • Java虛擬機啓動時被標明爲啓動類的類
  • JDK1.7開始提供的動態語言支持

**注意:調用ClassLoader類的loadClass()方法加載一個類,並不是對類的主動使用,不會導致類的初始化。

總結:

今天主要對類加載的雙親委託機制以及類初始化的時機進行了探討。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章