jvm原理(5)類加載器深入解析及重要特性剖析

類進入內存的過程:
這裏寫圖片描述

  • 類的加載的最終產品是位於內存中的Class對象。
  • Class對象封裝了類在方法區內的數據結構,並且向Java程序員提供了訪問方法區的數據的接口。類是反射的入口。

    有兩種類型的類加載器:

    • Java虛擬機自帶的加載器
      • 根類加載器(Bootstrap)
      • 擴展類加載器(Extension)
      • 系統(應用)類加載器(System)
        • 用戶自己定義的類加載器
        • java.lang.ClassLoader的子類
        • 用戶可以定製的加載方式

    類的加載:

  • 類的加載器並不需要等到某個類被“首次主動使用”時再加載它。

    怎麼理解這句話呢,我們就拿第一篇 文章的例子說明:
    我們加上-XX:+TraceClassLoading 參數

    public static void main(String[] args) {
        System.out.println(MyChild.str);
    }
}

class MyParent1{
    public static String str  = "hello world";
    static{
        System.out.println("MyParent1 static block");
    }
}

class MyChild extends  MyParent1{
    public static String str2 = "welcome";
    static{
        System.out.println("MyChild static bloack");
    }
}

運行結果:
這裏寫圖片描述
雖然子類沒有被初始化,但是通過日誌可以看出子類已經被加載了。

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

類的驗證:
- 類的驗證的內容
- 類文件的結構檢查
- 語義檢查
- 字節碼檢查
- 二進制兼容性的驗證

類的準備:
這裏寫圖片描述

類的初始化:
這裏寫圖片描述
這裏寫圖片描述

類的初始化步驟:
- 假如這個類還沒有被加載和鏈接,那就先進行加載和鏈接
- 假如類存在直接父類,並且這個父類還沒有被初始化,那就先初始化直接父類
- 假如類中存在初始化語句,那就依次執行這些初始化語句

類的初始化時機:
(1)主動使用(七種)

創建類的實例。
訪問某個類或者接口的靜態變量,或者對該靜態變量賦值。
調用類的靜態方法。
反射 (如Class.forName(“com.test.Test”))
初始化一個類的子類
Jav啊虛擬機啓動時被標明爲啓動類的類(Java Test)
JDK1.7開始提供的動態 語言支持:
Java.lang.invoke.MethodHandle實例的解析結果REF_getStattic REF_putStatic REF_invokeStatic句柄對應的類如果沒有初始化,則初始化(瞭解)
除了以上七中情況,其他使用Java類的方式都被看做是對類的被動使用,都不會導致類的初始化(指的是加載,連接,初始化這個步驟的初始化)

(2)
這裏寫圖片描述
驗證第一條:在初始化一個類時,並不會先初始化它所實現的接口

public class MyTest5 {
/*
詳情:http://blog.csdn.net/wzq6578702/article/details/79382182
 */
    public static void main(String[] args) {
        System.out.println(MyChild5.b);
    }
}

interface MyParent5{
    public static final int a =4;
    public static Thread thread = new Thread(){
        //代碼塊 當Thread的匿名類初始化的時候 代碼塊會被執行(每new一個類代碼塊都會執行一次)
        {
            System.out.println("MyParent5 invoked!");
        }
    };
}

class MyChild5 implements  MyParent5{
    public static  int b =  5;
}

輸出:

5

如果我們把MyParent5和MyChild5都改爲Class呢?
答案是輸出:

MyParent5 invoked!
5

因爲子類的初始化會想初始化父類
然後我們如果把MyChild5的b改爲【public static final int b = 5;】這樣運行結果是什麼呢?
答案是:

5

因爲b是常量,常量會編譯的時候就會放在MyTest5的常量池當中,甚至可以刪除MyParent5和MyChild5的Class文件。
再進一步:

public class MyTest5 {
/*
詳情:http://blog.csdn.net/wzq6578702/article/details/79382182
當一個接口初始化時並不要求其父接口完成了初始化
只有在真正用到父接口的時候(如引用接口中定義的常量時),纔會初始化。
 */
    public static void main(String[] args) {
        System.out.println(MyChild5.b);
    }
}

interface Grandapa{
    public static Thread thread = new Thread(){
        //代碼塊 當Thread的匿名類初始化的時候 代碼塊會被執行(每new一個類代碼塊都會執行一次)
        {
            System.out.println("Grandapa invoked!");
        }
    };
}

interface MyParent5 extends  Grandapa{
    public static final int a =4;
    public static Thread thread = new Thread(){
        //代碼塊 當Thread的匿名類初始化的時候 代碼塊會被執行(每new一個類代碼塊都會執行一次)
        {
            System.out.println("MyParent5 invoked!");
        }
    };
}

class MyChild5 implements  MyParent5{
    public static   int b =  5;
}

打印結果是什麼呢,肯定是先5吧
如果把Grandapa、MyParent5全部改成Class呢:

public class MyTest5 {
    public static void main(String[] args) {
        System.out.println(MyChild5.b);
    }
}

class Grandapa{
    public static Thread thread = new Thread(){
        //代碼塊 當Thread的匿名類初始化的時候 代碼塊會被執行(每new一個類代碼塊都會執行一次)
        {
            System.out.println("Grandapa invoked!");
        }
    };
}

class MyParent5 extends  Grandapa{
    public static final int a =4;
    public static Thread thread = new Thread(){
        //代碼塊 當Thread的匿名類初始化的時候 代碼塊會被執行(每new一個類代碼塊都會執行一次)
        {
            System.out.println("MyParent5 invoked!");
        }
    };
}

class MyChild5 extends  MyParent5{
    public static   int b =  5;
}

打印:

Grandapa invoked!
MyParent5 invoked!
5

以爲子類運行的時候會想去初始化它的父類。
驗證:在初始化一個接口是,並不會先初始化它的父接口


interface  MyGrandpa5_1{
    public static Thread thread = new Thread(){
        {
            System.out.println("MyGrandpa5_1 invoked!");
        }
    };
}

interface  MyParent5_1 extends MyGrandpa5_1 {
    public static Thread thread = new Thread(){
        {
            System.out.println("MyParent5_1 invoked!");
        }
    };
}
public class MyTest5 {
    public static void main(String[] args) {
        System.out.println(MyParent5_1.thread);
    }
}

打印結果:

MyParent5_1 invoked!
Thread[Thread-0,5,main]

可以看到MyGrandpa5_1並沒有被初始化。
(3)只有當程序訪問的靜態變量或靜態方法確實在當前類或當前接口中定義時,纔可以認爲是對類或接口的主動使用。

類加載器
這裏寫圖片描述
這裏寫圖片描述
除了以上虛擬機自帶的類加載器外,用戶還可以定製自己的類加載器。Java提供了抽象類Java.lang.ClassLoader,所有用戶自動以的類加載器都應該繼承ClassLoader類。
這裏寫圖片描述

發佈了179 篇原創文章 · 獲贊 41 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章