談?wù)劇皶?huì)”的三個(gè)層次
這里談?wù)勎依斫獾摹皶?huì)”的三個(gè)層次:

第一層:了解這門語言的語法、寫法,我把它叫做 hello world 級(jí)別;
第二層:了解這門語言的優(yōu)劣勢(shì)以及它的生態(tài),了解這門語言的能力范圍,我把它叫做?應(yīng)用 級(jí)別;
第三層:了解這門語言的底層運(yùn)行機(jī)制,這有利于對(duì)程序進(jìn)行調(diào)優(yōu),以及當(dāng)程序遇到了比較罕見的問題時(shí)能夠從根上分析解決它。我把它叫做?掌握?級(jí)別。
在簡歷上寫掌握某種語言的,一般面試官也會(huì)問一些很深入原理的問題,個(gè)人認(rèn)為比較合理。自己作為一個(gè)15年一線Java開發(fā),自認(rèn)為有資格把掌握J(rèn)ava寫在簡歷上。今天,我就來聊聊我對(duì)雙親委派機(jī)制一些理解。
Java類加載機(jī)制
首先我們需要思考一件事,我們編寫的Java代碼,是如何在各種各樣的操作系統(tǒng)上運(yùn)行起來的。

Java文件通過Javac編譯成class文件,這種中間碼被稱為字節(jié)碼。然后由JVM加載字節(jié)碼。這個(gè)過程就稱為類加載。
運(yùn)行時(shí),由解釋器將字節(jié)碼解釋為一行行的機(jī)器碼來執(zhí)行。在程序運(yùn)行期間,即時(shí)編譯器會(huì)針對(duì)熱點(diǎn)代碼,將該部分字節(jié)碼編譯成機(jī)器碼以獲取更高的執(zhí)行效率。在整個(gè)運(yùn)行時(shí),解釋器和即時(shí)編譯器相互配合,使程序幾乎能達(dá)到和編譯型語言幾乎一樣的執(zhí)行速度。這個(gè)部分交給專業(yè)的編譯器開發(fā)人員來做,咱們本篇不做深入講解。
到此上面那張圖就講完了,不要問我右上角那兩個(gè)表情是怎么回事。就是發(fā)現(xiàn)編輯的時(shí)候竟然可以添加表情,覺得好玩就試試看。
類的生命周期
在詳細(xì)講解之前,我們明確一下類加載流程的目的。站在高處去看,就是把一份被javac編譯過的文件通過加載,生成某種形式的class文件的數(shù)據(jù)結(jié)構(gòu)送進(jìn)內(nèi)存。程序可以調(diào)用這個(gè)數(shù)據(jù)結(jié)構(gòu)來構(gòu)造出Java對(duì)象。這個(gè)過程是在運(yùn)行時(shí)進(jìn)行的,也是Java動(dòng)態(tài)拓展性的根基。

上面這張圖表現(xiàn)了類的整個(gè)生命周期。而類加載呢,只包含了加載、鏈接和初始化三個(gè)階段。加載只是類加載的第一個(gè)環(huán)節(jié),兩者要注意區(qū)分。解析部分是靈活的,它可以在初始化環(huán)節(jié)之前或者之后進(jìn)行,實(shí)現(xiàn)后期綁定。類加載的其他環(huán)節(jié)的順序是不可改變的。
加載
加載是一個(gè)讀取class文件,將其轉(zhuǎn)化為某種靜態(tài)數(shù)據(jù)結(jié)構(gòu)而存儲(chǔ)在方法區(qū)內(nèi),并在堆中生成一個(gè)便于用戶調(diào)用的Java對(duì)象的過程。
這里值得注意的是,這個(gè)Java文件不一定是本地文件,泛指各種來源的二進(jìn)制流,比如網(wǎng)絡(luò)、數(shù)據(jù)庫或者比如動(dòng)態(tài)代理技術(shù)這樣的即時(shí)生成的class文件。
驗(yàn)證
驗(yàn)證的步驟很多,上面的圖畫得不完全準(zhǔn)確。對(duì)文件格式的校驗(yàn)其實(shí)是發(fā)生在加載階段的。通過才能順利加載。順利加載并不代表JVM完全認(rèn)可了這個(gè)類,還要進(jìn)行語法和語義上的分析,保證這個(gè)類不會(huì)危害JVM,這是對(duì)元數(shù)據(jù)和字節(jié)碼上的驗(yàn)證。在解析階段,還會(huì)進(jìn)行符號(hào)引用的驗(yàn)證。隨著JVM版本的升高,驗(yàn)證過程也在被不斷豐富。
準(zhǔn)備
準(zhǔn)備就是為靜態(tài)變量賦初始值,注意這里的初始值是JVM默認(rèn)初始值,是固定的,不是咱們寫代碼時(shí)的那個(gè)初始值。這里有個(gè)比較容易混淆的概念。
Java內(nèi)存規(guī)范定義了方法區(qū)這種抽象概念。主流的JVM實(shí)現(xiàn)如HotSpot在JDK8之前使用永久代這種在堆中開辟專門空間的實(shí)現(xiàn)方式,JDK8之后使用元空間這種直接內(nèi)存取代。
HotSpot的實(shí)現(xiàn):類的元信息、常量池、靜態(tài)變量等都存在在JDK8之前都存在在永久代這種方法區(qū)的具體實(shí)現(xiàn)中。JDK8之后,常量池、靜態(tài)變量被從方法區(qū)移除,轉(zhuǎn)移到了堆中。元信息這些依然保留在方法區(qū),具體的存儲(chǔ)方式改成了元空間。
解析
解析是將符號(hào)引用替換為直接引用。
靜態(tài)解析
符號(hào)引用就是假如類A引用了類B,加載階段是靜態(tài)解析,這時(shí)候B還沒有被放到JVM內(nèi)存中,這時(shí)候A引用的只是代表B的符號(hào),這是符號(hào)引用。
直接引用就是類A在解析階段發(fā)現(xiàn)自己引用了B,如果這個(gè)時(shí)候B還沒被加載。就是直接觸發(fā)B的類加載,之后B的符號(hào)引用會(huì)被替換成實(shí)際地址。這被稱為直接引用。
動(dòng)態(tài)解析
本文類的生命周期部分引出了后期綁定這個(gè)概念。后期綁定其實(shí)就是動(dòng)態(tài)解析。如果代碼使用了多態(tài)。B是一個(gè)抽象類或者接口,A就不能知道究竟要用哪個(gè)來替換,只能等到實(shí)際發(fā)生調(diào)動(dòng)時(shí)在進(jìn)行實(shí)際地址的替換。這就是為什么有的解析發(fā)生在初始化之后。
總結(jié)
類加載的過程今天就講這些。咱們來回顧一下類加載的五個(gè)階段。

從JVM的角度看,加載的讀取二進(jìn)制流和初始化階段,是開放了主導(dǎo)權(quán)給用戶的。用戶可以使用動(dòng)態(tài)代理等手段選擇是否這個(gè)階段進(jìn)行加載。還可以使用多態(tài)的手段選擇是否在這個(gè)階段進(jìn)行初始化。而剩下的所有部分都是JVM內(nèi)部完成的。
此時(shí)你可以閉上眼睛回顧一下類加載的五個(gè)階段,是不是不用死記硬背也能了然于胸了。