走進JVM-類加載過程

??在馮·諾依曼定義的計算機模型中抢蚀,任何程序都需要加載到內(nèi)存才能與 CPU進交流牛隅。字節(jié)碼.class 文件同樣需要加到內(nèi)存中,才可以實例化類振坚。“兵馬未動斋扰,糧草先行“渡八。ClassLoader 正是準(zhǔn)備糧草的先行軍啃洋,它的使命就是提前加載cass類文件到內(nèi)存中。在類加載時屎鳍,使用的是 Parents Delegation Model宏娄,譯為雙親委派樓型,如果意譯的話逮壁,則譯作“溯源委派加載模型”更加貼切孵坚。
??Java 的類加載器是一個運行時核心基礎(chǔ)設(shè)施模塊,如下圖 所示窥淆,主要是在啟動之初進行類的 Load卖宠、Link 和 Init,即加載忧饭、鏈接扛伍、初始化。
??第一步词裤,Load 階段讀取類文件產(chǎn)生二進制流刺洒,并轉(zhuǎn)化為特定的數(shù)據(jù)結(jié)構(gòu),初步校驗 cafe babe 魔法數(shù)亚斋、常量池作媚、文件度、是否有父類等帅刊,然后創(chuàng)建對應(yīng)類的 java.lang.Class 實例纸泡。
??第二步,Link 階段包括驗證赖瞒、準(zhǔn)備女揭、解析三個步驟。驗證是更詳細的校驗栏饮,比如final 是否合規(guī)吧兔、類型是否正確、靜態(tài)變量是否合理等袍嬉,準(zhǔn)備階段是為靜態(tài)變量分配內(nèi)存境蔼,并設(shè)定默認值,解析類和方法確保類與類之間的相互引用正確性伺通,完成內(nèi)存結(jié)構(gòu)布局箍土。
??第三步,Init 階段執(zhí)行類構(gòu)造器 <clinit> 方法罐监,如果賦值運算是通過其他類的靜態(tài)方法來完成的吴藻,那么會馬上解析另外一個類,在虛擬機棧中執(zhí)行完畢后通過返回值進行賦值弓柱。


java類加載過程

??類加載是一個將.class字節(jié)碼文件實例化成 Class 對象并進行相關(guān)初始化的過程沟堡。在這個過程中侧但,JVM 會初始化繼承樹上還沒有被初始化過的所有父類,并且會執(zhí)行這個鏈路上所有未執(zhí)行過的靜態(tài)代碼塊航罗、靜態(tài)變量賦值語句等禀横。某些類在使用時,可以按需由類加載器進行加載伤哺。
??全小寫的class是關(guān)鍵字,用來定義類,而首字母大寫的 Class,它是所有 class的類燕侠。這句話理解起來有難度者祖,是因為類已經(jīng)是現(xiàn)實世界中某種事物的抽象立莉,為什么這個象還是另外一個類Class 的對象?示例代碼如下:

public class ClassTest {
    // 數(shù)組類型有一個魔法屬性:length 來獲取數(shù)組長度
    private static int[] array = new int[3];
    private static int length = array.length;
    //任何小寫 class 定義的類,也有一個魔法屬性: class ,來獲取此類的大寫 Class 類對象
    private static Class<One> one = One.class;
    private static Class<Another> another =Another.class;

    public static void main(String[] args) throws Exception {
        // 通過newInstance 方法創(chuàng)建One 和Another 的類對象 (第1處)
        One oneObject = one.newInstance();
        oneObject.call();

        Another anotherObject = another.newInstance();
        anotherObject.speak();


        // 通過one 這個大寫的 Class 對象七问,獲取私有成員屬性對象 Field (第2處)
        Field privateFieldInOne = one.getDeclaredField("inner");
        // 設(shè)置私有對象可以訪問和修改 (第3處)
        privateFieldInOne.setAccessible(true);
        privateFieldInOne.set(oneObject, "world changed.");
        // 成功修改類的私有屬性 inner 變量值為 world changed.
        System.out.println(oneObject.getInner());
    }

}

class One {
    private String inner = "time files.";

    public void call() {
        System.out.println("hello world.");
    }

    public String getInner() {
        return inner;
    }
}

class Another {
    public void speak() {
        System.out.println("easy coding.");
    }
}

執(zhí)行結(jié)果如下:
hello world.
easy coding.
world changed.

  • 第1處說明:Class類下的newlnstance()在JDK9中已經(jīng)置為過時蜓耻,使用getDeclaredConstruclor().newInstance()的方式。這里看重說明一下new與newInstance()的區(qū)別械巡。new 是強類型校驗刹淌,可以調(diào)用任何構(gòu)造方法,在使用new操作的時候讥耗,這個類可以沒有被加載過有勾。而Class類下的newlinstance()是弱類型,只能調(diào)用無參數(shù)構(gòu)造方法,如果沒有默認構(gòu)造方法古程,就拋出InstantiationException異常;如果此構(gòu)造方法沒有權(quán)限訪問蔼卡,則拋出IllegalAccessException 異常。Java通過類加截器把類的實現(xiàn)與類的定義進行解耦挣磨,所以是實現(xiàn)面向接口編程雇逞、依賴倒置的必然選擇。
  • 第2處說明:可以使用類似的方式獲取其他聲明茁裙,如注解塘砸、方法等。如下圖所示


    類的反射信息
  • 第3處說明: private 成員在類外是否可以修改? 通過 setAccessible(true)操作即可使用大寫Class類的set方法修改其值晤锥。如果沒有這一步,則拋出如下異常
Exception in thread "main" java.lang.IllegalAccessException: class com.linkmiao.iot.demo.test.d202311.ClassTest cannot access a member of class com.linkmiao.iot.demo.test.d202311.One with modifiers "private"
    at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:361)

??通過以上示例掉蔬,對于Class 這個“類中之王”,我們有一定的了解? 那么回到類加載中矾瘾,類加載器是如何定位到具體的類文件并讀取的呢?
??類加載器類似于原始部落結(jié)構(gòu)女轿,存在權(quán)力等級制度。最高的一層是家族中威望最高的 Bootstrap霜威,它是在JVM 啟動時創(chuàng)建的谈喳,通常由與操作系統(tǒng)相關(guān)的本地代碼實現(xiàn)是最根基的類加載器,負責(zé)裝載最核心的Java 類戈泼,比如Object婿禽、System赏僧、String等;第二層是在JDK9版本中扭倾,稱為 Platform ClassLoader淀零,即平臺類加載器,用以加載些擴展的系統(tǒng)類膛壹,比如XML驾中、加密、壓縮相關(guān)的功能類等模聋,JDK9之前的加載器是Extension ClassLoader; 第三層是Application ClassLoader 的應(yīng)用類加載器肩民,主要是加載用戶定義的CLASSPATH路徑下的類。第二链方、三層類加載器為 Java 語言實現(xiàn)持痰,用戶也可以自定義類加載器。查看本地類加載器的方式如下:

        // 正在使用的類加載器: jdk.internal.loader.ClassLoaders$AppClassLoader@61064425
        ClassLoader c = TestWhoLoad.class.getClassLoader();
        System.out.println(c);
        // AppClassLoader 的父加載器是 PlatformClassLoader
        ClassLoader c1 = c.getParent();
        System.out.println(c1);
        // PlatformClassLoader 的父加載器是 Bootstrap祟蚀。它是使用C++ 來實現(xiàn)的工窍,返回null
        ClassLoader c2 = c1.getParent();
        System.out.println(c2);

代碼上方的注釋內(nèi)容為JDK11的執(zhí)行結(jié)果。在JDK8環(huán)境中前酿,執(zhí)行結(jié)果如下
sun.misc.Launcher$AppClassLoader@14dad5dc

sun.misc.Launcher$ExtClassLoader@6e0be858
null
??最高一層的類加載器Bootstrap 是通過C/C++ 實現(xiàn)的患雏,并不存在于JVM體系內(nèi)所以輸出為 null。類加載器具有等級制度罢维,但是并非繼承關(guān)系淹仑,以組合的方式來復(fù)用父加載器的功能,這也符合組合優(yōu)先原則言津,詳細的雙親委派模型如圖所示攻人。


雙親委派模型

??低層次的當(dāng)前類加載器,不能覆蓋更高層次類加載器已經(jīng)加載的類悬槽。如果低層次的類加載器想加載一個未知類怀吻,要非常禮貌地向上逐級詢問:“請問,這個類已經(jīng)加載了嗎?”被詢問的高層次類加載器會自問兩個問題:第一初婆,我是否已加載過此類?第二蓬坡,如果沒有,是否可以加載此類?只有當(dāng)所有高層次類加載器在兩個問題上的答案均為“否”時磅叛,才可以讓當(dāng)前類加載器加載這個未知類屑咳。如上圖所示,左側(cè)箭頭向上逐級詢問是否已加載此類弊琴,直至 Bootstrap ClassLoader兆龙,然后向下逐級嘗試是否能夠加載此類,如果都加載不了敲董,則通知發(fā)起加載請求的當(dāng)前類加載器紫皇,準(zhǔn)予加載慰安。在右側(cè)的三個小標(biāo)簽里,列舉了此層類加載器主要加載的代表性類庫聪铺,事實上不止于此化焕。通過如下代碼可以查看 Bootstrap 所有已經(jīng)加載的類庫:

        URL[] urLs = sun.misc.Launcher.getBootstrapClassPath().getURLs();
        for (java.net.URL url : urLs) {
            System.out.println(url.toExternalForm());
        }

執(zhí)行結(jié)果如下:
file:/C:/Program%20Files/Java/jdk1.8.0_261/jre/lib/resources.jar
file:/C:/Program%20Files/Java/jdk1.8.0_261/jre/lib/rt.jar
file:/C:/Program%20Files/Java/jdk1.8.0_261/jre/lib/sunrsasign.jar
file:/C:/Program%20Files/Java/jdk1.8.0_261/jre/lib/jsse.jar
file:/C:/Program%20Files/Java/jdk1.8.0_261/jre/lib/jce.jar
file:/C:/Program%20Files/Java/jdk1.8.0_261/jre/lib/charsets.jar
file:/C:/Program%20Files/Java/jdk1.8.0_261/jre/lib/jfr.jar
file:/C:/Program%20Files/Java/jdk1.8.0_261/jre/classes
Bootstrap 加載的路徑可以追加,不建議修改或刪除原有加載路徑铃剔。在JVM 中加如下啟動參數(shù)撒桨,則能通過 Class.forName 正常讀取到指定類,說明此參數(shù)可以增加Bootstrap的類加載路徑:-

-Xbootclasspath/a:/users/yangguanbao/book/egsyCoding/byJdk11/src

在學(xué)習(xí)了類加載器的實現(xiàn)機制后键兜,知道雙親委派模型并非強制模型凤类,用戶可以自定義類加載器,在什么情況下需要自定義類加載器呢?
(1)隔離加載類蝶押。在某些框架內(nèi)進行中間件與應(yīng)用的模塊隔離踱蠢,把類加載到不同的環(huán)境。比如棋电,阿里內(nèi)某容器框架通過自定義類加載器確保應(yīng)用中依賴的jar包會影響到中間件運行時使用的jar 包。
(2)修改類加載方式苇侵。類的加載模型并非強制赶盔,除 Bootstrap 外,其他的加載非一定要引入榆浓,或者根據(jù)實際情況在某個時間點進行按需進行動態(tài)加載于未。
(3)擴展加載源。比如從數(shù)據(jù)庫陡鹃、網(wǎng)絡(luò)烘浦,甚至是電視機機頂盒進行加載。
(4)防止源碼泄露萍鲸。Java 代碼容易被編譯和篡改闷叉,可以進行編譯加密。那么類加載器也需要自定義脊阴,還原加密的字節(jié)碼握侧。
實現(xiàn)自定義類加載器的步驟,繼承 ClassLoader嘿期,重寫 findClass0方法品擎,調(diào)用defineClass0方法。一個簡單的類加載器實現(xiàn)的示例代碼如下:

public class CustomClassLoader extends ClassLoader {
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] result = getClassFromCustomPath(name);
            if (result == null) {
                throw new FileNotFoundException();
            } else {
                return defineClass(name, result, 0, result.length);
            }
        } catch (Exception e) {
            e.printStackTrace();

        }
        throw new ClassNotFoundException(name);
    }

    private byte[] getClassFromCustomPath(String name) {
        // 從自定義路徑中加載指定類
        return null;
    }

    public static void main(String[] args) {
        CustomClassLoader customClassLoader = new CustomClassLoader();
        try {
            Class<?> clazz = Class.forName("One", true, customClassLoader);
            Object obj = clazz.newInstance();
            System.out.println(obj.getClass().getClassLoader());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

執(zhí)行的結(jié)果:
classloader.CustomClassLoader@5e481248
??由于中間件一般都有自己的依賴jar 包备徐,在同一個工程內(nèi)引用多個框架時萄传,往往被迫進行類的加載。按某種規(guī)則jar 包的版本被統(tǒng)一指定蜜猾,導(dǎo)致某些類存在包路徑類名相同的情況秀菱,就會引起類沖突西设,導(dǎo)致應(yīng)用程序出現(xiàn)異常。主流的容器類框架都會自定義類加載器答朋,實現(xiàn)不同中間件之間的類隔離贷揽,有效避免了類沖突。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末梦碗,一起剝皮案震驚了整個濱河市禽绪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌洪规,老刑警劉巖印屁,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異斩例,居然都是意外死亡雄人,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門念赶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來础钠,“玉大人,你說我怎么就攤上這事叉谜∑煊酰” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵停局,是天一觀的道長很钓。 經(jīng)常有香客問我,道長董栽,這世上最難降的妖魔是什么码倦? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮锭碳,結(jié)果婚禮上袁稽,老公的妹妹穿的比我還像新娘。我一直安慰自己工禾,他們只是感情好运提,可當(dāng)我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著闻葵,像睡著了一般民泵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上槽畔,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天栈妆,我揣著相機與錄音,去河邊找鬼。 笑死鳞尔,一個胖子當(dāng)著我的面吹牛嬉橙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播寥假,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼市框,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了糕韧?” 一聲冷哼從身側(cè)響起枫振,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎萤彩,沒想到半個月后粪滤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡雀扶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年杖小,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片愚墓。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡予权,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出转绷,到底是詐尸還是另有隱情伟件,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布议经,位于F島的核電站,受9級特大地震影響谴返,放射性物質(zhì)發(fā)生泄漏煞肾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一嗓袱、第九天 我趴在偏房一處隱蔽的房頂上張望籍救。 院中可真熱鬧,春花似錦渠抹、人聲如沸蝙昙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奇颠。三九已至,卻和暖如春放航,著一層夾襖步出監(jiān)牢的瞬間烈拒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留荆几,地道東北人吓妆。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像吨铸,于是被迫代替她去往敵國和親行拢。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容

  • JVM類加載機制 概述 類加載過程 加載 通過類的全限定名獲取類的二進制流 將靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)...
    東溪95閱讀 3,011評論 0 15
  • 類加載子系統(tǒng)的作用 1诞吱、類加載器子系統(tǒng)負責(zé)從文件系統(tǒng)或者網(wǎng)絡(luò)中加載class文件舟奠,class文件在文件開頭有特定的...
    蔣斌文閱讀 227評論 0 1
  • 一、JVM類加載過程 JVM類加載過程如下圖: JVM類加載過程分為:加載 狐胎、鏈接 鸭栖、初始化 、使用 握巢、卸載 這五...
    碼農(nóng)晴天閱讀 407評論 0 0
  • Java.JVM.Spring_DubboxMVC晕鹊、MVP、MVVM這些模式是為了解決開發(fā)過程中的實際問題而提出來...
    燕京博士閱讀 165評論 0 0
  • 我們都知道JDBC規(guī)范定義在java的核心包rt.jar中暴浦,rt.jar是由啟動類加載器去加載的溅话,jdbc的驅(qū)動管...
    liyefei2020閱讀 360評論 1 0