【JVM類加載】類的初始化和類加載器雙親委托機(jī)制

以此遞進(jìn),先加載test9調(diào)用子類,先初始化父類,類的初始化7中之一。
有兩種類型的類加載器
java虛擬機(jī)自帶的加載器
- 根類加載器(Bootstrap)
 - 擴(kuò)展類加載器(Extension)
 - 系統(tǒng)(應(yīng)用)類加載器(System)(2,3都屬于Launcher類的內(nèi)部類)
 
用戶自定義的類加載器
- java.lang.ClassLoader的子類。
 - 用戶可以定制類的加載方式。
 - 包括自定義類加載器在構(gòu)造的時(shí)候在構(gòu)造方法中傳入了一個(gè)父類加載。
 
類的加載器并不需要等到某個(gè)類被“首次主動(dòng)使用”時(shí)再加載它(個(gè)人理解 加載不一定初始化,初始化一定加載)。

類的加載
- jvm規(guī)范允許類加載器在預(yù)料某個(gè)類將要被使用時(shí)就預(yù)先加載他,如果在預(yù)先加載的過程中遇到了.class文件缺失或存在錯(cuò)誤,類加載器必須在程序首次主動(dòng)使用該類時(shí)才報(bào)告錯(cuò)誤(LinkageError錯(cuò)誤)。
 - 如果這個(gè)類一直沒有被程序主動(dòng)使用,那么類加載器就不會(huì)報(bào)告錯(cuò)誤。
 
類被加載后,就進(jìn)入鏈接階段。鏈接就是將已經(jīng)讀入到內(nèi)存的類的二進(jìn)制數(shù)據(jù)合并到虛擬機(jī)的運(yùn)行時(shí)環(huán)境中去。
類的初始化類的初始化步驟
- 假如這個(gè)類還沒有被加載和連接,那就先進(jìn)行加載和連接。
 - 假如類存在直接父類,并且這個(gè)父類還沒有被初始化,那就先初始化直接父類。
 - 假如類中存在初始化語句,那就依次執(zhí)行這些初始化語句。
 
類的初始化時(shí)機(jī)
主動(dòng)使用(7種,重要)除了七種情形,其他使用java類的方式都被看做是被動(dòng)使用,不會(huì)導(dǎo)致類的初始化。

初始化時(shí)機(jī)
jdk1.8在之后首次調(diào)用接口的靜態(tài)方法和default方法也會(huì)導(dǎo)致接口初始化。

初始化時(shí)機(jī)
示例:
public class Test5 {
    public static void main(String[] args) {
          //驗(yàn)證初始化一個(gè)類時(shí),并不會(huì)先初始化它所實(shí)現(xiàn)的接口
//        System.out.println(MyChild5.b);
        //驗(yàn)證初始化一個(gè)接口時(shí),并不會(huì)初始化它所實(shí)現(xiàn)的接口
        System.out.println(MyChild5_1.thread);
    }
}
interface MyParcnt5{
    public static Thread thread = new Thread(){
        {
            System.out.println("MyParcnt5 invoked");
        }
    };
    public static final int a = 5;
}
//interface MyChild5 extends MyParcnt5{
//
//    public static int b = new Random().nextInt(4);
//}
class MyChild5 implements MyParcnt5{
    //此時(shí)MyChild5 被加載并沒有被初始化  (加載不一定初始化,初始化一定加載)
    public static int b = 6;
}
interface MyParcnt5_1{
    public static Thread thread = new Thread(){
        {
            System.out.println("MyParcnt5_1 invoked");
        }
    };
}
interface MyChild5_1 extends MyParcnt5_1 {
    public static Thread thread = new Thread(){
        {
            System.out.println("MyChild5_1 invoked");
        }
    };
}類加載器雙親委托機(jī)制詳解
他們之間存在的是包含關(guān)系 不是繼承關(guān)系樹形結(jié)構(gòu)。
public abstract class ClassLoader {
    private static native void registerNatives();
    static {
        registerNatives();
    }
    // The parent class loader for delegation
    // Note: VM hardcoded the offset of this field, thus all new fields
    // must be added *after* it.
    private final ClassLoader parent;ClassLoader中有一個(gè)成員變量定義著雙親 因?yàn)槌烁惣虞d器每個(gè)加載器都繼承于ClassLoder 所以每個(gè)ClassLoaderd都存在著對(duì)應(yīng)的雙親 所以他們之間存在的是包含關(guān)系 不是繼承關(guān)系樹形結(jié)構(gòu)。

加載過程
在雙親委托機(jī)制中,各個(gè)加載器按照父子關(guān)系形成了樹形結(jié)構(gòu),除了根類加載器之外,其余的類加載器都有且只有一個(gè)父加載器。

加載過程
每個(gè)類都需要類加載器去加載,如果有父類,先讓父類去加載,如此向上追溯,直到根類加載器,然后根類加載器嘗試去加載,加載成功賊結(jié)束,加載失敗,又往下,一層層的嘗試去加載,最終如果都沒有加載成功則報(bào)錯(cuò)。

加載方式
回顧之前學(xué)的知識(shí)點(diǎn)。
public class Test7 {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> clazz = Class.forName("java.lang.String");
        System.out.println(clazz.getClassLoader());//獲取類加載器,如果是Bootstrap ClassLoader 根加載器加載可能會(huì)返回null
        Class<?> clazs = Class.forName("com.example.demo.com.jvm.C");
        System.out.println(clazs.getClassLoader());//sun.misc.Launcher$AppClassLoader@18b4aac2 基本上工程編寫的類都是由系統(tǒng)(應(yīng)用)加載器AppClassLoader加載的
    }
}
class C {
}打印結(jié)果:
null
sun.misc.Launcher$AppClassLoader@18b4aac2class FinalTest {
    public static final int x = 3;
//    public static final int x = new Random().nextInt(3);
    static {
        System.out.println("FinalTest static block");
    }
}
public class Test8 {
    public static void main(String[] args) {
        System.out.println(FinalTest.x);
    }
}/*
    public static final int x = 3; 時(shí) 此時(shí)在編譯階段 常量x 會(huì)被放進(jìn)Test8的常量池 直接拿來使用 		    FinalTest 并不會(huì)被初始化
    3
 */
/*
    public static final int x = new Random().nextInt(3);時(shí) new Random().nextInt(3)生成隨機(jī)數(shù)實(shí)在運(yùn)行時(shí)生成的 所以 Test8和FinalTest  有聯(lián)系 此時(shí)FinalTest類會(huì)被加載
    FinalTest static block
    1
*/class Parent {
    static int a = 3;
    static {
        System.out.println("Parent static block");
    }
}
class Child extends Parent {
    static int b = 4;
    static {
        System.out.println("Child static block");
    }
}
public class Test9 {
    static {
        System.out.println("Test9 static block");
    }
    public static void main(String[] args) {
        System.out.println(Child.b);
    }
}以此遞進(jìn) 先加載test9 調(diào)用子類 先初始化父類 類的初始化7種之一。
/*
以此遞進(jìn) 先加載test9 調(diào)用子類 先初始化父類 類的初始化7中之一
 Test9 static block
 Parent static block
 Child static block
 4
 */class Parent1 {
    static int a = 3;
    static {
        System.out.println("Parent static block");
    }
}
class Child1 extends Parent1 {
    static int b = 4;
    static {
        System.out.println("Child static block");
    }
}
public class Test10 {
    static {
        System.out.println("Test10 static block");
    }
    public static void main(String[] args) {
        Parent1 parent1;
        System.out.println("===========");
        parent1 = new Parent1();
        System.out.println("===========");
        System.out.println(parent1.a);
        System.out.println("===========");
        System.out.println(Child1.b);
    }
}類只會(huì)首次加載才會(huì)初始化。
/*
類只會(huì)首次加載才會(huì)初始化
Test10 static block
===========
Parent static block
===========
3
===========
Child static block
4
 */class Parent3 {
    static int a = 3;
    static {
        System.out.println("Parent3 static block");
    }
    static void doSomething() {
        System.out.println("do doSomething");
    }
}
class Child3 extends Parent3 {
    static {
        System.out.println("Child3 static block");
    }
}
public class Test11 {
    public static void main(String[] args) {
        System.out.println(Child3.a); //類名.父類靜態(tài)變量和靜態(tài)方法 表示對(duì)父類的主動(dòng)使用 此時(shí)子類并不初始化
        System.out.println("---------------");
        Child3.doSomething();
    }
}類名.父類靜態(tài)變量和靜態(tài)方法 表示對(duì)父類的主動(dòng)使用 此時(shí)子類并不初始化。
//調(diào)用ClassLoader類的loadClass方法加載一個(gè)類,并不是對(duì)類的主動(dòng)使用,不會(huì)導(dǎo)致類的初始化
public class Test12 {
    public static void main(String[] args) throws ClassNotFoundException {
        ClassLoader loder = ClassLoader.getSystemClassLoader(); //獲取系統(tǒng)加載器
        Class<?> aClass = loder.loadClass("com.example.demo.com.jvm.CL");//加載對(duì)應(yīng)類 除卻7種都是被動(dòng)使用 不會(huì)初始化
        System.out.println(aClass);
        System.out.println("-----------------");
        Class<?> aClass1 = Class.forName("com.example.demo.com.jvm.CL");//7種之一
        System.out.println(aClass1);
    }
}只有反射初始化CL了 調(diào)用ClassLoader類的loadClass方法加載一個(gè)類,并不是對(duì)類的主動(dòng)使用,不會(huì)導(dǎo)致類的初始化。
/*
只有反射初始化CL了 調(diào)用ClassLoader類的loadClass方法加載一個(gè)類,并不是對(duì)類的主動(dòng)使用,不會(huì)導(dǎo)致類的初始化
class com.example.demo.com.jvm.CL
-----------------
Class cl
class com.example.demo.com.jvm.CL
 */不同的類加載器作用與動(dòng)作分析
隱式加載: 程序在運(yùn)行過程中碰到通過new等方式生成對(duì)象時(shí),隱式調(diào)用類裝載器加載對(duì)應(yīng)的類到j(luò)vm中。
顯式裝載: 通過class.forname()等方法,顯示加載需要的類。
類加載的動(dòng)態(tài)體現(xiàn)
- 一個(gè)應(yīng)用程序總是由n多個(gè)類組成,Java程序啟動(dòng)時(shí),并不是一次把所有的類全部加載后再運(yùn)行,它總是先把保證程序運(yùn)行的基礎(chǔ)類一次性加載到j(luò)vm中,其它類等到j(luò)vm用到的時(shí)候再加載,這樣的好處是節(jié)省了內(nèi)存的開銷,因?yàn)閖ava最早就是為嵌入式系統(tǒng)而設(shè)計(jì)的,內(nèi)存寶貴,這是一種可以理解的機(jī)制,而用到時(shí)再加載這也是java動(dòng)態(tài)性的一種體現(xiàn)。
 - public static ClassLoader getSystemClassLoader();獲取系統(tǒng)類加載器。
 
返回用于委派的系統(tǒng)類加載器getParent() 返回父類加載器進(jìn)行委派。
public class Test13 {
    public static void main(String[] args) {
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();//獲取系統(tǒng)類加載
        System.out.println(classLoader);
        while (null != classLoader) {
            classLoader = classLoader.getParent(); //獲取父加載器 因?yàn)槭褂酶虞d器時(shí) 返回值是用null來表示 所以循環(huán)結(jié)束
            System.out.println(classLoader);
        }
    }
}/*
jdk自帶 的三個(gè)類加載器
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@1eb44e46
null 并沒有繼承classLoader
*/public class Test14 {
    public static void main(String[] args) throws IOException {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();//獲取當(dāng)前線程的上下文類加載器 通常是獲取那個(gè)用于啟動(dòng)應(yīng)用的類加載器
        String res = "com/example/demo/com/jvm.Test13.class";
        Enumeration<URL> resource = classLoader.getResources(res);//獲取 類的資源
        while (resource.hasMoreElements()) {
            URL url = resource.nextElement();
            System.out.println(url);
        }
        System.out.println("--------獲取類加載器的有對(duì)應(yīng)的的類-----------");
        Class<Test14> test14Class = Test14.class;
        System.out.println(test14Class.getClassLoader()); //sun.misc.Launcher$AppClassLoader@18b4aac2
    }
}獲取類加載器的幾種方法。

獲取類加載的幾種方式















 
 
 












 
 
 
 