深入理解Java虛擬機器 --- 類載入機制

ayu0v0發表於2024-11-08

類的生命週期

類的生命週期:載入→驗證→準備→解析→初始化→使用→解除安裝

類載入的時機

關於在什麼情況下需要需要開始類載入過程的第一個階段"載入",虛擬機器並沒有進行強制約束,這點交給虛擬機器的具體實現來自由把握。

但嚴格規定了有且只有六種情況必須立即對類進行"初始化":

(位元組碼指令:new、getstatic、putstatic、invokestatic)

1、遇到new關鍵字例項化物件的時候。

2、讀取或設定一個型別的靜態欄位(被final修飾、已在編譯期把結果放入常量池的靜態欄位除外)的時候。

3、呼叫一個型別的靜態方法的時候。

4、使用java.lang.reflect包的方法對型別進行反射呼叫的時候。

5、當初始化類的時候時,發現其父類還沒有進行初始化,需要先觸發父類的初始化。

6、當虛擬機器啟動時,使用者需要指定一個要執行的主類(包括main()),那麼就會觸發該類的初始化。

類的主動使用和被動使用

主動使用:對類主動進行初始化。在上述六種情況。

被動使用:被動使用不會初始化類,但可能會導致類的載入。

1、透過子類引用訪問父類的靜態欄位,不會造成子類的初始化。

2、使用陣列定義引用類,不會觸發引用類的初始化。

Tips:建立陣列的位元組碼指令為**newarray**。

3、訪問類的靜態常量

常量在編譯的時候會存入呼叫類的常量池中,本質上沒有直接引用到定義常量的類

類載入的過程

載入

目的:

1、透過一個類的全限定名來獲取描述該類的二進位制位元組流。

2、將這個位元組流所代表的靜態儲存結構轉化為方法區的執行時資料結構。

3、在記憶體中生成一個代表該類的java.lang.Class物件,作為方法區這個類的各種資料的訪問入口。

類載入器

設計團隊想把類載入階段中"透過一個類的全限定名來獲取描述該類的二進位制位元組流"放到Java虛擬機器外部去做,以便讓應用程式自己決定如何去獲取所需類

實現這個動作的程式碼被稱為類載入器

作用:載入類。

image.png

引導類載入器

Bootstrap ClassLoader:是用C語言寫的,不是ClassLoader的子類。被稱為根裝載器。它是在Java虛擬機器啟動後初始化的,它主要負責載入%JAVA_HOME%/jre/lib,-Xbootclasspath引數指定的路徑以及%JAVA_HOME%/jre/classes中的類。

Bootstrap ClassLoader只載入包名為java、javax、sun等開頭的類。

擴充套件類載入器

Extension ClassLoader:Bootstrap Loader載入ExtClassLoader,並將ExtClassLoader的父載入器設定為Bootstrap Loader。其是用Java寫的。ExtClassLoader主要載入%JAVA_HOME%/jre/lib/ext,此路徑下的所有classes目錄以及java.ext.dirs系統變數指定的路徑中類庫。

繼承於ClassLoader類。

系統類載入器

Application ClassLoader:Bootstrap Loader載入ExtClassLoader後,就會載入AppClassLoader,並將AppClassLoader的父載入器指定為ExtClassLoader。AppClassLoader主要負責載入classpath所指定的位置的類或者是jar文件,它也是Java程式預設的類載入器。

雙親委派機制

雙親委派機制:指先委託父類裝載器去尋找目標類,如果父類裝載器尋找不到,再從自己負責的路徑下尋找目標類。

雙親委派機制主要在ClassLoader中的loadClass方法中體現。

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        //用synchronized保證載入時類只載入一個Class類
        synchronized (getClassLoadingLock(name)) {
            // 首先,檢查請求的類是否已經被載入過了
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    //雙親委派機制
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // 如果父類載入器丟擲ClassNotFoundException
                    // 說明父類載入器無法完成載入請求
                }

                if (c == null) {
                    // 在父類載入器無法載入時
                    // 再呼叫本身的findClass方法來進行類載入
                    c = findClass(name);
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

雙親委派模型的優點:

  • Java類伴隨著它的類載入器一起具備了一種帶有優先順序的層級關係,透過這種層級關係可以避免類的重複載入。

  • 保護程式安全,防止核心API被篡改。

雙親委派模型的缺點:

  • 頂層的ClassLoader無法訪問底層ClassLoader載入的類。

雙親委派模型的破壞:

第一次:還沒有出現雙親委派模型之前。

第二次:為了解決頂層ClassLoder無法訪問底層ClassLoader載入的類,引入了執行緒上下文類載入器

執行緒上下文載入器:透過Thread類的setContextClassLoader()方法設定,如果建立執行緒時未設定,那麼會從父執行緒中繼承,**如果在應用程式的全域性範圍都沒有設定過,預設為應用類載入器**。

第三次:由於使用者對於程式動態性的追求導致的。如:程式碼熱替換、模組熱部署等。

驗證

目的:確保Class檔案位元組流中包含的資訊符合規範,保證不會危害虛擬機器的安全。

檔案格式校驗:

魔數、版本號、常量池

後設資料校驗:

類是否有父類(除了Object,所以類都有父類)、類是否實現了介面的所有方法

位元組碼驗證:

目的:透過資料流分析和控制流資料分析,確定程式語義是合法的、符合邏輯的。

符號引用驗證:

目的:確保解析行為能正常執行

準備

為類中定義的靜態變數分配記憶體並設定類變數初始值的階段。

解析

解析過程是將Java虛擬機器將常量池內的符號引用替換為直接引用

符號引用:用字面量來描述所引用的目標。

直接引用:可以直接指向目標的指標。

初始化

初始化階段就是執行類構造器()方法的過程。

相關文章