0x01.某廠面試題
請闡述Java自帶哪些加載器以及對應(yīng)的職責(zé)?
(1)Bootstrap ClassLoader(啟動類加載器):負(fù)責(zé)加載<JAVA_HOME>\lib目錄,或-Xbootclasspath指定目錄下的jar。
特殊說明:JVM認(rèn)為合理的jar文件名才會被加載,例如:rt.jar、tools.jar,文件名不符合的jar即使放在上述目錄下也不會被加載。
(2)Extension ClassLoader(擴展類加載器):負(fù)責(zé)加載<JAVA_HOME>\lib\ext目錄,或java.ext.dirs指定目錄下的jar。
特殊說明:Java9以后引入了模塊化機制,此加載器被此機制取代。
(3)Application ClassLoader(應(yīng)用程序類加載器):負(fù)責(zé)加載用戶類路徑(ClassPath)上的jar包。
此題出處:周志明的《深入理解Java虛擬機》-7.4.類加載器,變形題很多:
例如:請你選出哪些不是Java自帶的類加載器;
例如:給出各類類加載器的描述、職責(zé),選錯誤的;
例如:將上述類加載器的特殊說明作為干擾選項;——這種題可以無情坑殺很多程序猿...
0x02.不要背,要理解
至此,我們似乎沒必要大費周章地解讀這樣一道記憶題。背下來不就可以了嗎?筆者認(rèn)為不是:
發(fā)展24年的Java及JVM,歷經(jīng)若干版本的商業(yè)斗爭(A new future for Java),衍生出若干語(語)言(法)特(陷)性(阱)。
"背下來就行"的前提有二,可惜對于Java這些語(語)言(法)特(陷)性(阱)都不成立:
(1)知識輸入是無二義性的、與時俱進(jìn)的。
(2)知識輸入是有限集。
所以,筆者堅定的認(rèn)為唯Java程序員最有資格喊出"我禿了,我也變強了"。
所以,筆者堅定的認(rèn)為唯"深刻理解JVM"(即,深刻理解語法特性到JVM層,甚至操作系統(tǒng)層),是Java程序員的最有效的防禿良藥。

0x03.從韓國星工廠看類加載機制的全貌
From《深入理解Java虛擬機》-周志明
Java虛擬機將描述類的數(shù)據(jù)從Class文件加載到內(nèi)存,并對數(shù)據(jù)進(jìn)行校驗、轉(zhuǎn)換解析和初始化,最終變成被虛擬機可以直接使用的Java類型,這個過程被稱作Java虛擬機的類加載機制......與那些在編譯時需要進(jìn)行連接的語言不同,在Java語言里面,類型的加載、連接和初始化過程都是在程序運行期間完成的......用戶可以通過Java預(yù)置的或自定義類加載器...作為其程序代碼的一部分。
從這段經(jīng)典且嚴(yán)謹(jǐn)?shù)拿枋觯覀兛梢猿橄?個問題,以此宏觀地獲得類加載機制的全貌:
問題1:加載什么?
問題2:加載得到了什么?
問題3:從哪里加載?
問題4:加載到哪里去?
問題5:何時加載?
問題6:加載流程有幾步?
問題7:加載流程如何實現(xiàn)?
問題8:類加載器的約束?
(1)加載什么?
我們在.java文件中編寫了一堆代碼,被轉(zhuǎn)換成字節(jié)碼(.class文件),JVM加載就是這個字節(jié)碼。
這個字節(jié)碼描述的信息[1]包括(但不限于):
有哪些類、哪些接口;
每個類有哪些屬性、行為;
類和類的關(guān)系;
...
這些信息就是程序員定義的"模板",在程序運行時,當(dāng)需要基于類A創(chuàng)建實例對象a時,JVM就要先找到類A對應(yīng)的"模板"。
這些"模板",就像韓國星工場包裝女團時的"劇本",每個女星都是基于某個"模板"創(chuàng)建的實例對象,女星的身高、體重、才藝就是"模板"中描述的"屬性、行為"。

[1]:本文聚焦類加載機制的全貌,以后再其他文章展開
(2)加載得到了什么?
字節(jié)碼加載到內(nèi)存后得到的信息內(nèi)容還是(1)中提到的字節(jié)碼中包含的信息,但信息形式變了,抽象、簡單地理解為JVM能認(rèn)識的數(shù)據(jù)結(jié)構(gòu)[2]。
這就像娛樂公司的策劃團隊在電腦上寫好了女團出道的"劇本",女團的經(jīng)紀(jì)人要無差錯地仔細(xì)閱讀"劇本"的電子件,把"劇本"存儲在大腦中,以備后續(xù)使用。

[2]:本文聚焦類加載機制的全貌,以后再其他文章展開
(3)從哪里加載?
用一張表,說明字節(jié)碼文件與經(jīng)紀(jì)人的類比關(guān)系:
| 字節(jié)碼文件在哪里? | 劇本在哪里? |
|---|---|
| 在本地磁盤上,JVM在指定目錄去查找 | 在電腦上,經(jīng)紀(jì)人去指定目錄去打開看 |
| 在網(wǎng)絡(luò)上,JVM在指定網(wǎng)絡(luò)路徑上去查找 | 在某個網(wǎng)站上,經(jīng)紀(jì)人去這個網(wǎng)站上看 |
| JVM根據(jù)某些規(guī)則動態(tài)生成 | 經(jīng)紀(jì)人自行加戲,隨機應(yīng)變 |
這一點也是JVM給Java程序員的自由,很多著名的產(chǎn)品就是在這個點上做了文章。
(4)加載到哪里去?
這個問題最好回答:內(nèi)存,具體指的是JVM的運行時數(shù)據(jù)區(qū),再具體一點是運行時數(shù)據(jù)區(qū)中的方法區(qū)[3]

[3]:本文聚焦類加載機制的全貌,以后再其他文章展開
(5)何時加載?
類加載發(fā)生在運行時,這也是Java、C#這類語言推崇的動態(tài)性。
動態(tài)性是一把雙刃劍,運行時加載意味著需要"邊執(zhí)行,邊連接"。
這就好像女團經(jīng)紀(jì)人不是提前把"劇本"爛熟于胸,而是到了女團演出現(xiàn)場,才掏出"劇本",現(xiàn)場理解、現(xiàn)場執(zhí)行。
以現(xiàn)實生活的經(jīng)驗,這樣的經(jīng)紀(jì)人,看起來不太靠譜。
當(dāng)然,動態(tài)性一定是源于某些特定場景和需求,Java為了靠譜,也引出了虛擬機中另一個大的課題:JIT(即時編譯),圍繞著"如何提升運行時的編譯效率"會有很多有趣的故事。
(6)加載流程有幾步?
在(2)中我們提到女團的經(jīng)紀(jì)人要無差錯地仔細(xì)閱讀"劇本"的電子件,關(guān)鍵詞是無差錯
我們再用一張表,類比一下[4]
| 加載流程? | 經(jīng)紀(jì)人閱讀劇本的流程? |
|---|---|
| STEP1.加載(Load):將字節(jié)碼文件變成內(nèi)存的數(shù)據(jù)結(jié)構(gòu) | STEP1.加載:經(jīng)紀(jì)人打開劇本,讀了一遍劇本 |
| STEP2.連接(Link) | STEP2.連接 |
| STEP2.1.驗證(Verify):驗證文件格式、元數(shù)據(jù)、字節(jié)碼、符號引用,最終保證JVM的安全可靠 | STEP2.1.驗證:經(jīng)紀(jì)人若有所思的翻了翻劇本,確認(rèn)沒拿錯劇本吧?不會是包裝男團的劇本吧? |
| STEP2.2.準(zhǔn)備(Prepare):"static"的內(nèi)存分配與賦值 | STEP2.2.準(zhǔn)備:女團不管包裝幾個人(實例),她們肯定需要一個訓(xùn)練房(static變量),現(xiàn)在就租好 |
| STEP2.3.解析(Resolve):符號引用轉(zhuǎn)為直接引用。例如:類A有個成員變量,類型是類B,類B在哪呢? | STEP2.3.解析:劇本中Lisa的那一頁說她出道前在一個泰國舞團,泰國舞團的資料在劇本附件2,經(jīng)紀(jì)人如果需要查看泰國舞團資料的時候,就知道去翻附件2 |
| STEP3.初始化(Init):執(zhí)行構(gòu)造函數(shù),觸發(fā)關(guān)聯(lián)類的解析 | STEP3.初始化:經(jīng)紀(jì)人前期準(zhǔn)備差不過了,可以給Lisa(實例)打個電話:Lisa同學(xué),快來做一下出道前的準(zhǔn)備(構(gòu)造函數(shù)) |
表格中存在不嚴(yán)謹(jǐn)(通俗)的表達(dá),但相信讀者已經(jīng)關(guān)注到了兩個核心要點:
(1)加載流程有幾步
(2)每一步的作用是什么
[4]:上述加載流程每一步存在一些JVM實現(xiàn)的細(xì)節(jié),例如:上述步驟一定是順序執(zhí)行的嗎?例如:直接引用需要反復(fù)解析嗎?這些都有必要詳細(xì)解讀,本文暫不展開。

(7)加載流程如何實現(xiàn)?
至此,終于可以和本文開頭的面試題呼應(yīng)上了,前文我們探討了
加載什么=>加載得到了什么=>從哪里加載=>加載到哪里去=>何時加載=>加載流程有幾步
那么這都是JVM的規(guī)范、規(guī)格,怎么實現(xiàn)?類加載器就是類加載機制的具體實現(xiàn)。
不同類型的加載器相當(dāng)于公司不同的角色,都去自己專屬的文件夾去查找并加載類
Bootstrap加載器相當(dāng)于經(jīng)濟公司老板
Extension加載器相當(dāng)于經(jīng)紀(jì)人
Application加載器相當(dāng)于女團藝人
(8)類加載器的約束?
既然有不同的類加載器,它們之間必然有配合關(guān)系,否則兩個人干了同一個工作,對外口徑還不同,咋辦?
JVM定義了這種配合關(guān)系,還取了個高大上的名字"雙親委派模型",筆者認(rèn)為這種模型應(yīng)該叫"保姆式管理模型"
還是一張表類比一下:
| 雙親委派模型 | 保姆式管理模型 |
|---|---|
| STEP1.程序需要加載一個類 | STEP1.記者問Lisa:某天晚上和你一起上車的神秘男子是誰? |
| STEP2.Application加載器一臉懵逼的問Extention加載器,領(lǐng)導(dǎo),你能加載不? | STEP2.Lisa一臉懵逼的問經(jīng)紀(jì)人,歐巴,你幫我回答一下嘛? |
| STEP3.Extention加載器無助的看著Bootstrap加載器,領(lǐng)導(dǎo),你能加載不? | STEP3.經(jīng)紀(jì)人無助的看看經(jīng)紀(jì)公司老板,老大,還是你回答一下吧 |
| STEP4.Bootstrap加載器無奈的搖搖頭:試試看吧,我能加載就加載,不能的話,Extension,還是你自己試試 | STEP4.老板搖搖頭:試試看吧,我能回答就回答,不能答復(fù),經(jīng)紀(jì)人,你自己試試 |
| STEP5.(假設(shè)Bootstrap不能加載)Extension無奈的搖搖頭:我自己試試吧,不能加載的話,Application,你自己試試吧。 | STEP6.(假設(shè)老板不能答復(fù))經(jīng)紀(jì)人苦笑了:我試試吧,不能回答,Lisa,你自己上吧。 |
| STEP7.(假設(shè)Extension不能加載):Application自己來吧 | STEP7.(假設(shè)經(jīng)紀(jì)人不能回答)Lisa嬌羞道的回答記者:恩~~倫家也不知道...反正不是蔡徐坤... |

雙親委派模型,本質(zhì)上是說"領(lǐng)導(dǎo)先上,領(lǐng)導(dǎo)不行我再上"。
這種管理風(fēng)格,自然也有弊端。歷史上有4次破壞雙親委派模型[5]的事件。
破壞雙親委派模型的本質(zhì),是"管理團隊的風(fēng)格多樣化",例如:"扁平化團隊"、"先干了再說團隊"等等。
[5]:每種"管理風(fēng)格"也各有利弊,這也是JVM發(fā)展歷程中可以仔細(xì)學(xué)習(xí)的技術(shù)點,筆者在后續(xù)文章中再來探討。
0x03 小結(jié)
我們從一道面試題開始,通過8個問題看到了類加載機制的全貌。
加載什么->加載得到了什么->從哪里加載->加載到哪里去->何時加載->加載流程有幾步->加載流程如何實現(xiàn)->類加載器的約束
本文篇幅有限,還留下了一些重要、有趣的細(xì)節(jié),未來筆者還會繼續(xù)展開
字節(jié)碼描述的信息
JVM能認(rèn)識的數(shù)據(jù)結(jié)構(gòu)
方法區(qū)的結(jié)構(gòu)
加載流程每一步存在一些JVM實現(xiàn)的細(xì)節(jié)
破壞雙親委派模型
最后,筆者還想談?wù)?理解優(yōu)于記憶"的個人觀點:深入理解JVM的原理對于實戰(zhàn)的意義就是"先驗知識",是"性能調(diào)優(yōu)、內(nèi)存泄露、OSGI"等疑難雜癥、高級框架的"基礎(chǔ)",如果能看到這類面試題背后的Why、What、How,您就獲得了探尋計算機秘境的不二法門。