JVM知識(shí)體系-01類(lèi)加載機(jī)制

一、思維導(dǎo)圖

01類(lèi)加載機(jī)制.png

二、大綱

2.1 類(lèi)的加載是什么?

  • 將類(lèi)(.class)文件中的二進(jìn)制數(shù)據(jù)讀入內(nèi)存,并放在運(yùn)行時(shí)數(shù)據(jù)庫(kù)的方法區(qū)內(nèi),在堆區(qū)創(chuàng)建java.lang.Class對(duì)象(封裝類(lèi)在方法區(qū)的數(shù)據(jù)結(jié)構(gòu))。

  • 類(lèi)加載最終產(chǎn)品是堆區(qū)的Class對(duì)象(封裝了類(lèi)在方法區(qū)的數(shù)據(jù)結(jié)構(gòu),并向Java程序員提供了訪(fǎng)問(wèn)方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)的接口)。

2.2 類(lèi)的生命周期(重要)

2.2.1 類(lèi)的生命周期圖

類(lèi)的生命周期.png

注意:順序執(zhí)行的意思是開(kāi)始,而不是完成后進(jìn)行下一步

2.2.2 過(guò)程

1. 加載

  • 查找并加載類(lèi)的二進(jìn)制數(shù)據(jù),在Java堆中創(chuàng)建java.lang.Class對(duì)象

    JVM需要做的三件事

    1. 通過(guò)類(lèi)的全限定名獲取其定義的二進(jìn)制字節(jié)流
    2. 將此字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)
    3. 在Java堆中創(chuàng)建java.lang.Class對(duì)象,作為訪(fǎng)問(wèn)方法區(qū)數(shù)據(jù)的入口
  • 獲取class文件的5種方式

    1. 本地系統(tǒng)中直接加載
    2. 網(wǎng)絡(luò)下載.class文件
    3. 從zip,jar等歸檔文件中加載.class文件
    4. 專(zhuān)有數(shù)據(jù)庫(kù)提取.class文件
    5. Java源文件動(dòng)態(tài)編譯為.class文件

注意:此階段可控性最強(qiáng),可用系統(tǒng)自帶類(lèi)加載器也可自定義類(lèi)加載器

2. 連接

2.1 驗(yàn)證

四個(gè)階段

  • 文件格式
    如0xCAFEBABE開(kāi)頭等
  • 元數(shù)據(jù)
    語(yǔ)義分析,如是否有父類(lèi)(不含Object)
  • 字節(jié)碼
    語(yǔ)義合法
  • 符號(hào)引用驗(yàn)證
    保證解析能正確執(zhí)行

注意:重要但不必須,如所引用類(lèi)經(jīng)反復(fù)驗(yàn)證,可采用-Xverifynone關(guān)閉

2.2 準(zhǔn)備

為類(lèi)的靜態(tài)變量分配內(nèi)存,并將其初始化默認(rèn)值

注意:

  • 僅包括類(lèi)變量(static),不含實(shí)例變量(在實(shí)例化時(shí)隨對(duì)象一塊分配在堆中)
  • 此初始值指數(shù)據(jù)類(lèi)型默認(rèn)的零值,非顯示賦予的值
  • 若類(lèi)字段的字段屬性表中存在ConstantValue屬性,即同時(shí)被final和static修飾,在準(zhǔn)備階段變量會(huì)被初始化為ConstantValue屬性做指定的值
2.3 解析

把類(lèi)中的符號(hào)引用(一組符號(hào)來(lái)描述)轉(zhuǎn)化為直接引用(直接指向目標(biāo)的指針、相對(duì)偏移量或一個(gè)間接定位到目標(biāo)的句柄)


直接指針訪(fǎng)問(wèn)對(duì)象.png

句柄訪(fǎng)問(wèn)對(duì)象.png
  • 符號(hào)引用

    • 接口
    • 字段
    • 類(lèi)方法
    • 接口方法
    • 方法類(lèi)型
    • 方法句柄
    • 調(diào)用點(diǎn)限制符
  • 加載順序不確定
    某些情況下在初始化之后,為了支持Java語(yǔ)言的運(yùn)行時(shí)綁定(動(dòng)態(tài)綁定或晚期綁定)

3. 初始化

為類(lèi)的靜態(tài)變量賦予正確的初始值

設(shè)定的兩種方式

  • 聲明類(lèi)變量是指定初始值
  • 使用靜態(tài)代碼塊為類(lèi)變量指定初始值

JVM初始化步驟

  • 若此類(lèi)未被加載和連接,則程序先加載并連接該類(lèi)
  • 若此類(lèi)的直接父類(lèi)未初始化,則先初始化其直接父類(lèi)
  • 若類(lèi)中有初始化語(yǔ)句,則系統(tǒng)依次執(zhí)行這些初始化語(yǔ)句

類(lèi)初始化時(shí)機(jī)

只有當(dāng)對(duì)類(lèi)的主動(dòng)使用的時(shí)候才會(huì)導(dǎo)致類(lèi)的初始化

  • 六種主動(dòng)使用
    1. 創(chuàng)建類(lèi)的實(shí)例,即new
    2. 訪(fǎng)問(wèn)某個(gè)類(lèi)或接口的靜態(tài)變量,或?qū)υ撿o態(tài)變量賦值
    3. 調(diào)用類(lèi)的靜態(tài)方法
    4. 反射(如Class.forName("com.insigim.Test"))
    5. 初始化某個(gè)類(lèi)的子類(lèi),則父類(lèi)也被初始化
    6. Java虛擬機(jī)啟動(dòng)時(shí)被標(biāo)明為啟動(dòng)類(lèi)的類(lèi)(Java Test),直接使用java.exe命令來(lái)運(yùn)行某個(gè)主類(lèi)

4. 使用

  • new出對(duì)象程序中使用

5. 卸載

  • 執(zhí)行垃圾回收

2.2.3 幾種情況下會(huì)結(jié)束生命周期

  1. 執(zhí)行System.exit()方法
  2. 程序正常執(zhí)行結(jié)束
  3. 程序執(zhí)行過(guò)程中遇到了異常或錯(cuò)誤而異常終止
  4. 由于操作系統(tǒng)出現(xiàn)錯(cuò)誤而導(dǎo)致Java虛擬機(jī)進(jìn)程終止

2.3 類(lèi)加載器

1. 幾種加載器的層次關(guān)系

類(lèi)加載器層次關(guān)系.png

2. 兩種角度分類(lèi)

2.1 JVM角度

啟動(dòng)類(lèi)加載器
  • 使用C++實(shí)現(xiàn)(僅限Hotspot)
  • 虛擬機(jī)自身一部分
所有其他類(lèi)加載器
  • Java語(yǔ)言實(shí)現(xiàn)
  • 獨(dú)立虛擬機(jī)之外
  • 繼承抽象類(lèi)java.lang.ClassLoader
  • 需由啟動(dòng)類(lèi)加載器加載到內(nèi)存中才能加載其他的類(lèi)

2.2 開(kāi)發(fā)人員角度

啟動(dòng)類(lèi)加載器
  • Bootstrap ClassLoader,C++實(shí)現(xiàn)
  • 負(fù)責(zé)加載存放在JDK\jre\lib下,或被-Xbootclasspath參數(shù)指定的路徑中,且能被JVM識(shí)別的類(lèi)庫(kù)(如rt.jar,所有java.開(kāi)頭的類(lèi))
  • 無(wú)法被Java程序直接引用
擴(kuò)展類(lèi)加載器
  • Extension ClassLoader,由sun.misc.launcher$ExtClassLoader實(shí)現(xiàn)
  • 負(fù)責(zé)加載JDK\jre\lib\ext目錄中,或java.ext.dirs系統(tǒng)變量指定的路徑中的所有類(lèi)庫(kù)(如javax.開(kāi)頭的類(lèi))
  • 開(kāi)發(fā)者可直接使用擴(kuò)展類(lèi)加載器
應(yīng)用程序類(lèi)加載器
  • Application ClassLoader,由sun.misc.Launcher$AppClassLoader實(shí)現(xiàn)
  • 負(fù)載加載用戶(hù)類(lèi)路徑(ClassPath)所指定的類(lèi)
  • 開(kāi)發(fā)者可直接使用該類(lèi)加載器,若應(yīng)用程序未自定義自己的類(lèi)加載器,此即為默認(rèn)的類(lèi)加載器

3. JVM類(lèi)加載機(jī)制(重要)

  • 全盤(pán)負(fù)責(zé)
    當(dāng)一個(gè)類(lèi)加載器加載類(lèi)時(shí),此類(lèi)所依賴(lài)和引用的其它Class都由此加載器負(fù)責(zé),除非顯示使用另一個(gè)類(lèi)加載器

  • 父類(lèi)委托
    先讓父類(lèi)加載器試圖加載,只有父類(lèi)無(wú)法加載,才嘗試從自己的類(lèi)路徑加載該類(lèi)

  • 緩存機(jī)制
    保證所有加載過(guò)的Class都被緩存,程序需使用時(shí)優(yōu)先從緩存中加載,沒(méi)有先找二進(jìn)制數(shù)據(jù)在堆中創(chuàng)建Class對(duì)象,加入緩存,所以每次Class修改需重啟JVM,修改才生效

2.4 類(lèi)的加載

有三種方式

  1. 命令行啟動(dòng)應(yīng)用時(shí)候由JVM初始化加載
  2. 通過(guò)Class.forName()方法動(dòng)態(tài)加載
  3. 通過(guò)ClassLoader.loadClass()方法動(dòng)態(tài)加載

Class.forName()與ClassLoader.loadClass()加載的區(qū)別?

  • Class.forName():將類(lèi)的.class文件加載到JVM中之外,還會(huì)對(duì)類(lèi)進(jìn)行解釋?zhuān)瑘?zhí)行類(lèi)中的static塊
  • ClassLoader.loadClass():只干一件事,就是將.class文件加載到JVM中,不執(zhí)行statiic中內(nèi)容(newInstance才會(huì)執(zhí)行)
  • Class.forName(name,initialize,loader)帶參函數(shù)也可控制是否加載static塊,并且只有調(diào)用newInstance()方法采用調(diào)用構(gòu)造函數(shù),創(chuàng)建類(lèi)的對(duì)象

2.5 雙親委派模型(重要)

工作流程

若類(lèi)加載器收到類(lèi)加載的請(qǐng)求,首先把請(qǐng)求委托父加載器去完成,依次向上,因此所有的類(lèi)加載請(qǐng)求最終都應(yīng)該被傳遞到頂層的啟動(dòng)類(lèi)加載器中,只有當(dāng)父加載器在它的搜索范圍內(nèi)沒(méi)有找到所需的類(lèi)時(shí)(無(wú)法加載),子加載器才嘗試自己去加載該類(lèi)

雙親委派機(jī)制

  1. 當(dāng)AppClassLoader加載一個(gè)class時(shí),先委派ExtClassLoader加載
  2. 當(dāng)ExtClassLoader加載一個(gè)class時(shí),先委派BootStrapClassLoader去加載
  3. 若BootStrapClassLoader加載失?。↗DK\jre\lib無(wú)法找到該類(lèi)),使用ExtClassLoader加載
  4. ExtClassLoader也加載失?。↗DK\jre\lib\ext無(wú)法找到該類(lèi)),則由AppClassLoader加載,若還失敗則報(bào)出異常ClassNotFoundException

源碼分析

public Class<?> loadClass(String name)throws ClassNotFoundException {
       return loadClass(name, false);
}

protected synchronized Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {
       // 首先判斷該類(lèi)型是否已經(jīng)被加載
       Class c = findLoadedClass(name);
       if (c == null) {
           //如果沒(méi)有被加載,就委托給父類(lèi)加載或者委派給啟動(dòng)類(lèi)加載器加載
           try {
               if (parent != null) {
                    //如果存在父類(lèi)加載器,就委派給父類(lèi)加載器加載
                   c = parent.loadClass(name, false);
               } else {
               //如果不存在父類(lèi)加載器,就檢查是否是由啟動(dòng)類(lèi)加載器加載的類(lèi),
              //通過(guò)調(diào)用本地方法native Class findBootstrapClass(String name)
                   c = findBootstrapClass0(name);
               }
           } catch (ClassNotFoundException e) {
            // 如果父類(lèi)加載器和啟動(dòng)類(lèi)加載器都不能完成加載任務(wù),才調(diào)用自身的加載功能
               c = findClass(name);
           }
       }
       if (resolve) {
           resolveClass(c);
       }
       return c;
   }

意義

  • 系統(tǒng)類(lèi)防止內(nèi)存中出現(xiàn)多份同樣的字節(jié)碼
  • 保證Java程序安全穩(wěn)定運(yùn)行

JDK1.9后的委派模型

JDK9后的類(lèi)加載器委派關(guān)系.png

2.6 自定義類(lèi)加載器

一般情況,由三種類(lèi)加載器互相配合進(jìn)行加載,如有必要,也可加入自定義類(lèi)加載器
  • 如應(yīng)用是通過(guò)網(wǎng)絡(luò)來(lái)傳輸Java類(lèi)的字節(jié)碼,為保證安全性,這些字節(jié)碼經(jīng)過(guò)加密處理,系統(tǒng)類(lèi)加載器無(wú)法加載,則需要自定義加載器實(shí)現(xiàn)
  • 一般繼承l(wèi)oadClasser類(lèi),只需重寫(xiě)findClass方法即可
  • 核心在于對(duì)字節(jié)碼文件的獲取,如果加密的字節(jié)碼則需要在該類(lèi)中對(duì)文件進(jìn)行解密
  • 傳遞的文件名是全限定性名稱(chēng)
  • 最好不要重寫(xiě)loadClass方法,容易破壞雙親委托模式
  • 不要把類(lèi)放在AppClassLoader路徑下,容易導(dǎo)致AppClassLoader類(lèi)加載器加載
因JVM自帶ClassLoader只懂從本地文件加載標(biāo)準(zhǔn)java cass文件,若編寫(xiě)自己的ClassLoader,需做到以下三點(diǎn)
  1. 執(zhí)行非置信代碼之前,自動(dòng)驗(yàn)證數(shù)字簽名
  2. 動(dòng)態(tài)地創(chuàng)建符合用戶(hù)特定需要的定制化構(gòu)建類(lèi)
  3. 從特定的場(chǎng)所所取得java class,如從數(shù)據(jù)庫(kù)中和網(wǎng)絡(luò)中
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容