類加載機(jī)制

一、類加載時(shí)機(jī)

一個(gè)類型從被加載到虛擬機(jī)內(nèi)存中開始掂榔,到卸載出內(nèi)存為止继效,整個(gè)生命周期將會(huì)經(jīng)歷 加載、驗(yàn)證装获、準(zhǔn)備瑞信、解析、初始化穴豫、使用凡简、卸載 七個(gè)階段。

其中驗(yàn)證精肃、準(zhǔn)備秤涩、解析三個(gè)部分統(tǒng)稱為連接。

六種情況必須立即對(duì)類進(jìn)行“初始化”

  1. 遇到 new司抱、getstatic溉仑、putstaticinvokestatic 這四條字節(jié)碼指令時(shí),如果類型沒有進(jìn)行初始化状植,則需要先觸發(fā)其初始化階段浊竟。能夠生成這四條指令的典型 Java 代碼場(chǎng)景有:
    • 使用 new 關(guān)鍵字實(shí)例化對(duì)象時(shí)
    • 讀取或設(shè)置一個(gè)類型的靜態(tài)字段(被 final 修飾、已在編譯器把結(jié)果放入常量池的靜態(tài)字段除外)時(shí)
    • 調(diào)用一個(gè)類型的靜態(tài)方法時(shí)
  2. 使用 java.lang.reflect 包的方法對(duì)類型進(jìn)行反射調(diào)用時(shí)津畸,如果類型沒有進(jìn)行過初始化振定,則需要先觸發(fā)其初始化。
  3. 當(dāng)初始化類時(shí)肉拓,如果發(fā)現(xiàn)其父類還沒有進(jìn)行過初始化后频,則需要先觸發(fā)其父類的初始化。
  4. 當(dāng)虛擬機(jī)啟動(dòng)時(shí)暖途,用戶需要指定一個(gè)要執(zhí)行的主類(包含main()方法的類)卑惜,虛擬機(jī)會(huì)先初始化這個(gè)主類。
  5. 當(dāng)使用 JDK 7 新加入的動(dòng)態(tài)語言支持時(shí)驻售,如果一個(gè) java.lang.invoke.MethodHandle 實(shí)例最后的解析結(jié)果為 REF_getStatic露久、REF_putStatic
    REF_invokeStatic欺栗、REF_newIncokeSpecial 四種類型的方法句柄毫痕,并且這個(gè)方法句柄對(duì)應(yīng)的類沒有進(jìn)行初始化征峦,則需要先觸發(fā)其初始化。
  6. 當(dāng)一個(gè)接口中定義了 JDK 8 新加入的默認(rèn)方法(被 default 關(guān)鍵字修飾的接口方法)時(shí)消请,如果有這個(gè)接口的實(shí)現(xiàn)類發(fā)生了初始化栏笆,那該接口要在其之前被初始化。

二臊泰、類加載過程

2.1蛉加、加載

“加載”階段是整個(gè)“類加載”過程中的一個(gè)階段。需要完成三件事情:

  1. 通過一個(gè)類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流缸逃。
  2. 將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)七婴。
  3. 在內(nèi)存中生成一個(gè)代表這個(gè)類的 java.lang.Class 對(duì)象,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)的訪問入口察滑。

加載階段既可以使用 Java 虛擬機(jī)內(nèi)置的引導(dǎo)類加載器來完成,也可以由用戶自定義的類加載器去完成修肠,開發(fā)人員通過自定義的類加載器去控制字節(jié)流的獲取方式(重寫一個(gè)類加載器的findClass()loadClass()方法)贺辰,實(shí)現(xiàn)根據(jù)自己的想法來賦予應(yīng)用程序獲取運(yùn)行代碼的動(dòng)態(tài)性。

數(shù)組類本身不通過類加載器創(chuàng)建嵌施,是由 Java 虛擬機(jī)直接在內(nèi)存中動(dòng)態(tài)構(gòu)造出來的饲化。

加載階段的典型應(yīng)用

  • 從 ZIP 壓縮包中讀取,最終發(fā)展出 JAR吗伤、WAR格式吃靠。
  • 從網(wǎng)絡(luò)中獲取,典型的是 Web Applet足淆。
  • 運(yùn)行時(shí)計(jì)算生成巢块,該場(chǎng)景使用最多的是動(dòng)態(tài)代理技術(shù)。
  • 由其他文件生成巧号,如 JSP生成對(duì)應(yīng)的 Class 文件族奢。
  • 從數(shù)據(jù)庫中讀取,比較少見丹鸿,如 某些中間件服務(wù)器(SAP Netweaver)越走。
  • 從加密文件中獲取,典型的防 Class 文件被反編譯的保護(hù)措施靠欢,通過加載時(shí)解密Class 文件來保障程序運(yùn)行邏輯不被窺探廊敌。

2.2、驗(yàn)證

確保 Class 文件的字節(jié)流中包含的信息符合《Java虛擬機(jī)規(guī)范》的全部約束要求门怪,保證這些信息被當(dāng)做代碼運(yùn)行后不會(huì)危害虛擬機(jī)自身的安全骡澈。

  1. 文件格式驗(yàn)證
  2. 元數(shù)據(jù)驗(yàn)證
  3. 字節(jié)碼驗(yàn)證
  4. 符號(hào)引用驗(yàn)證

2.3、準(zhǔn)備

正式為類中定義的變量(即靜態(tài)變量掷空,被static修飾的變量)分配內(nèi)存并設(shè)置類變量初始值的階段秧廉。

  1. 進(jìn)行內(nèi)存分配的僅包括類變量伞广,而不包括實(shí)例變量(實(shí)例變量隨對(duì)象實(shí)例化時(shí)一起分配在Java堆中)。
  2. 這里的初始值“通常情況”下是數(shù)據(jù)類型的零值疼电。
public static int value = 123;
// 準(zhǔn)備階段初始值為 0 而非 123嚼锄,賦值123 在初始化階段進(jìn)行

public static final int value = 123;
// 存在 ConstantValue 屬性,準(zhǔn)備階段會(huì)被初始化為 ConstantValue 屬性所指定的初始值蔽豺,這里即 123

2.4区丑、解析

Java 虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過程。

具體還包括:

  • 類或接口的解析
  • 字段解析
  • 方法解析
  • 接口方法解析

2.5修陡、初始化

直到初始化階段沧侥,Java 虛擬機(jī)才真正開始執(zhí)行類中編寫的Java 程序代碼,將主導(dǎo)權(quán)移交給應(yīng)用程序魄鸦。

初始化階段就是執(zhí)行類構(gòu)造器 <clinit>() 方法的過程宴杀。

三、類加載器

3.1拾因、雙親委派模型

加載階段旺罢,需要用到類加載器來將 class 文件里面的內(nèi)容搞到 JVM 中生成類對(duì)象。

雙親委派模型用一句話講就是子類加載器先讓父類加載器去查找該類來加載绢记,父類又繼續(xù)請(qǐng)求它的父類直到最頂層扁达,在父類加載器沒有找到所請(qǐng)求的類的情況下,子類加載器才會(huì)嘗試去加載蠢熄,這樣一層一層上去又下來跪解。

每個(gè)類加載器都有固定的查找類的路徑,在JDK8的時(shí)候一共有三種類加載器签孔。

  • 啟動(dòng)類加載器(Bootstrap ClassLoader)叉讥,它是屬于虛擬機(jī)自身的一部分,用 C++ 實(shí)現(xiàn)的饥追,主要負(fù)責(zé)加載目錄中或被 -Xbootclasspath 指定的路徑中的并且文件名是被虛擬機(jī)識(shí)別的文件节吮。它是所有類加載器的爸爸。
  • 擴(kuò)展類加載器(Extension ClassLoader)判耕,它是 Java 實(shí)現(xiàn)的透绩,獨(dú)立于虛擬機(jī),主要負(fù)責(zé)加載目錄中或被 java.ext.dirs 系統(tǒng)變量所指定的路徑的類庫壁熄。
  • 應(yīng)用程序類加載器(Application ClassLoader)帚豪,它是Java實(shí)現(xiàn)的,獨(dú)立于虛擬機(jī)草丧。主要負(fù)責(zé)加載用戶類路徑(classPath)上的類庫狸臣,如果我們沒有實(shí)現(xiàn)自定義的類加載器那這玩意就是我們程序中的默認(rèn)加載器。


    JVM_Parents-Delegation-Model

為什么要提出雙親委派模型昌执?
其實(shí)就是為了讓基礎(chǔ)類得以正確地統(tǒng)一地加載烛亦。
從上面的圖可以看出诈泼,如果你也定義了一個(gè) 類,通過雙親委派模式是會(huì)把這個(gè)請(qǐng)求委托給啟動(dòng)類加載器煤禽,它掃描目錄就找到了 jdk 定義的 類來加載铐达,所以壓根不會(huì)加載你寫的 類,這就可以避免一些程序不小心或者有意的覆蓋基礎(chǔ)類檬果。

雖說是子類父類瓮孙,但是加載器之間的關(guān)系不是繼承,而是組合选脊。

public abstract class ClassLoader {

    private final ClassLoader parent;
    
    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            // 先看看之前是否加載過
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    // 如果有父類就委托給父類
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        // 父類是空杭抠,說明是啟動(dòng)類加載器
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    // 如果父類找不到就自己找
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
}

在 JVM 中,類的唯一性是由類加載器實(shí)例和類的全限定名一同確定的恳啥,也就是說即使是同一個(gè)類文件加載的類偏灿,用不同的類加載器實(shí)例加載,在 JVM 看來這也是兩個(gè)類钝的。

3.2翁垂、破壞雙親委派模型

第一次破壞

在 jdk 1.2 之前,那時(shí)候還沒有雙親委派模型扁藕,不過已經(jīng)有了 ClassLoader 這個(gè)抽象類,所以已經(jīng)有人繼承這個(gè)抽象類疚脐,重寫 loadClass 方法來實(shí)現(xiàn)用戶自定義類加載器亿柑。

而在 1.2 的時(shí)候要引入雙親委派模型,為了向前兼容棍弄, loadClass 這個(gè)方法還得保留著使之得以重寫望薄,新搞了個(gè) findClass 方法讓用戶去重寫,并呼吁大家不要重寫 loadClass 只要重寫 findClass呼畸。

這就是第一次對(duì)雙親委派模型的破壞痕支,因?yàn)殡p親委派的邏輯在 loadClass 上,但是又允許重寫 loadClass蛮原,重寫了之后就可以破壞委派邏輯了卧须。

第二次破壞

第二次破壞指的是 JNDI、JDBC 之類的情況儒陨。

首先得知道什么是 SPI(Service Provider Interface)花嘶,它是面向拓展的,也就是說我定義了個(gè)規(guī)矩蹦漠,就是 SPI 椭员,具體如何實(shí)現(xiàn)由擴(kuò)展者實(shí)現(xiàn)。

像我們比較熟的 JDBC 就是如此笛园。

MySQL 有 MySQL 的 JDBC 實(shí)現(xiàn)隘击,Oracle 有 Oracle 的 JDBC 實(shí)現(xiàn)侍芝,我 Java 不管你內(nèi)部如何實(shí)現(xiàn)的,反正你們這些數(shù)據(jù)庫廠商都得統(tǒng)一按我這個(gè)來埋同,這樣我們 Java 開發(fā)者才能容易的調(diào)用數(shù)據(jù)庫操作州叠,所以在 Java 核心包里面定義了這個(gè) SPI。

而核心包里面的類都是由啟動(dòng)類加載器去加載的莺禁,但它的手只能摸到或Xbootclasspath指定的路徑中留量,其他的它鞭長(zhǎng)莫及。

而 JDBC 的實(shí)現(xiàn)類在我們用戶定義的 classpath 中哟冬,只能由應(yīng)用類加載器去加載楼熄,所以啟動(dòng)類加載器只能委托子類來加載數(shù)據(jù)庫廠商們提供的具體實(shí)現(xiàn),這就違反了自下而上的委托機(jī)制浩峡。

具體解決辦法是搞了個(gè)線程上下文類加載器可岂,通過默認(rèn)情況就是應(yīng)用程序類加載器,然后利用獲得類加載器來加載翰灾。

這就是第二次破壞雙親委派模型缕粹。

第三次破壞:熱部署

這次破壞是為了滿足熱部署的需求,不停機(jī)更新這對(duì)企業(yè)來說至關(guān)重要纸淮,畢竟停機(jī)是大事平斩。

OSGI 就是利用自定義的類加載器機(jī)制來完成模塊化熱部署,而它實(shí)現(xiàn)的類加載機(jī)制就沒有完全遵循自下而上的委托咽块,有很多平級(jí)之間的類加載器查找绘面。

第四次破壞:模塊化

在 JDK9 引入模塊系統(tǒng)之后,類加載器的實(shí)現(xiàn)其實(shí)做了一波更新侈沪。

當(dāng)收到類加載請(qǐng)求揭璃,會(huì)先判斷該類在具名模塊中是否有定義,如果有定義就自己加載了亭罪,沒的話再委派給父類瘦馍。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市应役,隨后出現(xiàn)的幾起案子情组,更是在濱河造成了極大的恐慌,老刑警劉巖箩祥,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件呻惕,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡滥比,警方通過查閱死者的電腦和手機(jī)亚脆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來盲泛,“玉大人濒持,你說我怎么就攤上這事键耕。” “怎么了柑营?”我有些...
    開封第一講書人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵屈雄,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我官套,道長(zhǎng)酒奶,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任奶赔,我火速辦了婚禮惋嚎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘站刑。我一直安慰自己另伍,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開白布绞旅。 她就那樣靜靜地躺著摆尝,像睡著了一般。 火紅的嫁衣襯著肌膚如雪因悲。 梳的紋絲不亂的頭發(fā)上堕汞,一...
    開封第一講書人閱讀 51,708評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音晃琳,去河邊找鬼讯检。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蝎土,可吹牛的內(nèi)容都是我干的视哑。 我是一名探鬼主播绣否,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼誊涯,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了蒜撮?” 一聲冷哼從身側(cè)響起暴构,我...
    開封第一講書人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎段磨,沒想到半個(gè)月后取逾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡苹支,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年砾隅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片债蜜。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡晴埂,死狀恐怖究反,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情儒洛,我是刑警寧澤精耐,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站琅锻,受9級(jí)特大地震影響卦停,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜恼蓬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一惊完、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧滚秩,春花似錦专执、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至桐腌,卻和暖如春拄显,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背案站。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工躬审, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蟆盐。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓承边,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親石挂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子博助,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355

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