1.類加載的目的:
一份被javac編譯過的class文本文件通過加載仰美,生成某種形式的Class數(shù)據(jù)結(jié)構(gòu)進(jìn)入內(nèi)存猾骡, 程序可以調(diào)用這個(gè)數(shù)據(jù)結(jié)構(gòu)來構(gòu)造出object即硼,這個(gè)過程是在運(yùn)行時(shí)進(jìn)行的钞诡,也是java動態(tài)拓展性的根基羡微。
一個(gè)類的生命周期:
- javac 編譯
- 加載
- 鏈接
- 初始化
- 使用
- 卸載
類加載包含了三個(gè)階段
- 加載
- 鏈接
- 初始化
鏈接包含三個(gè)步驟:
- 驗(yàn)證
- 準(zhǔn)備
- 解析
其中傻唾,解析步驟是靈活的投慈,他可以在初始化之前或者之后再進(jìn)行,實(shí)現(xiàn)所謂的‘后期綁定’策吠,其他環(huán)節(jié)則是不可改變的逛裤。
2.類加載的階段
加載
加載是一個(gè)讀取Class文件,將其轉(zhuǎn)化為某種靜態(tài)數(shù)據(jù)結(jié)構(gòu)存儲在方法區(qū)內(nèi)猴抹,并在堆中生成一個(gè)便于用戶調(diào)用的java.lang.Class類型的對象的過程带族。
注意:
這里的Class文件不僅僅是指的本地文件,泛指各種來源的二進(jìn)制流蟀给,比如從網(wǎng)絡(luò)蝙砌、數(shù)據(jù)庫、即時(shí)計(jì)算出來的class文件跋理,比如著名的就是 動態(tài)代理技術(shù)择克,就是使用到了即時(shí)計(jì)算出來的class,然后實(shí)例化代理對象前普,可以自由選擇二進(jìn)制流肚邢。
鏈接
- 驗(yàn)證
驗(yàn)證動作是有很多個(gè)步驟的,包括:文件格式驗(yàn)證拭卿、元數(shù)據(jù)字節(jié)碼驗(yàn)證骡湖、符號引用驗(yàn)證。
文件格式驗(yàn)證:其實(shí)是發(fā)生在加載階段的峻厚,如果通過响蕴,那么才能順利加載,順利加載后此時(shí)方法區(qū)內(nèi)雖然已經(jīng)存在了該class的靜態(tài)結(jié)構(gòu)惠桃,堆中也存在了該class類型的對象浦夷,但是這并不代表著JVM已經(jīng)完全認(rèn)可了這個(gè)類辖试。如果程序想要使用這個(gè)類那么就必須進(jìn)行鏈接,而鏈接的第一步就是進(jìn)一步對這個(gè)類進(jìn)行驗(yàn)證劈狐,到底對方法區(qū)內(nèi)的class靜態(tài)結(jié)構(gòu)進(jìn)行了哪些方面的驗(yàn)證罐孝。
元數(shù)據(jù)的驗(yàn)證、字節(jié)碼的驗(yàn)證:就是對class的靜態(tài)結(jié)構(gòu)進(jìn)行語法和語義上的分析懈息,保證其不會產(chǎn)生危害虛擬機(jī)的行為肾档。這兩個(gè)步驟驗(yàn)證通過,那么虛擬機(jī)會姑且認(rèn)為該class是安全的辫继,但是這并不意味著驗(yàn)證已經(jīng)完全結(jié)束了,還有一道對符號引用進(jìn)行驗(yàn)證的步驟俗慈。
符號引用驗(yàn)證:它是在解析階段內(nèi)發(fā)生的姑宽,而解析階段我們之前也提到過,它可以在初始化階段之前或者之后進(jìn)行闺阱。
驗(yàn)證其實(shí)包含了很多的步驟炮车,分散在各個(gè)不同的階段內(nèi),驗(yàn)證的內(nèi)容是會不斷發(fā)展的酣溃,除了這里提到的文本格式驗(yàn)證瘦穆、元數(shù)據(jù)字節(jié)碼驗(yàn)證、符號引用驗(yàn)證 四個(gè)環(huán)節(jié) 從低版本的虛擬機(jī)到現(xiàn)在 驗(yàn)證步驟其實(shí)已經(jīng)不斷的加入了各種機(jī)制赊豌。在未來扛或,虛擬機(jī)的開發(fā)人員可能會引入更多更完善更完美的策略。
- 準(zhǔn)備
在元數(shù)據(jù)碘饼、字節(jié)碼驗(yàn)證通過之后熙兔,虛擬機(jī)會姑且認(rèn)為該class是安全的,這時(shí)候會進(jìn)入準(zhǔn)備階段艾恼,準(zhǔn)備階段做的處理住涉,并不復(fù)雜,就是為該類型中定義的靜態(tài)變量賦0值钠绍,注意舆声,這里僅僅是靜態(tài)變量而不是成員變量,此時(shí)將會出現(xiàn)一個(gè)被太多人混淆和誤解的概念柳爽,我們這里就好好捋一捋媳握。
虛擬機(jī)內(nèi)存規(guī)范中的定義了方法區(qū)這種抽象概念,HotSpot這種主流虛擬機(jī)在JDK8之前使用了永久代這種具體的實(shí)現(xiàn)方式來實(shí)現(xiàn)方法區(qū)泻拦。在JDK8之后毙芜,棄用‘永久代’這種實(shí)現(xiàn)方式,采用元空間這種直接內(nèi)存取代争拐。
在JDK8之前腋粥,類的元信息晦雨、常量池、靜態(tài)變量等都存儲在 永久代這種具體實(shí)現(xiàn)中隘冲,而在JDK8及之后闹瞧,常量池、靜態(tài)變量被移除‘方法區(qū)’展辞,轉(zhuǎn)移到了堆中奥邮,元信息這些呢,依然保留在方法區(qū)內(nèi)罗珍,但是具體的存儲方式改成了元空間洽腺。
- 解析
該階段主要做的事情是將 符號引用 替換為 直接引用。什么是符號引用覆旱,什么是直接引用蘸朋?
當(dāng)一個(gè)Java類被編譯成Class之后,假如這個(gè)類稱為A扣唱,并且A中 引用了B藕坯,那么在編譯階段,A是不知道B有沒有被編譯的噪沙,而且此時(shí)B也一定沒有被加載炼彪,所以A肯定不知道B的實(shí)際地址,那么A怎么才能找到B呢正歼?辐马,此時(shí)在A的class文件中,將使用一個(gè)字符串S來代表B的地址朋腋,S就被稱為符號引用齐疙,在運(yùn)行時(shí)呢,如果A發(fā)生了類加載旭咽,到了解析階段會發(fā)現(xiàn)B還未被加載贞奋,那么將會觸發(fā)B的類加載,將B加載到虛擬機(jī)中穷绵,此時(shí)A中的B的符號引用將會被替換成B的實(shí)際地址轿塔,這被稱為直接引用,這樣也就能夠真正的調(diào)用B了仲墨。
Java通過后期綁定的方式來實(shí)現(xiàn)多態(tài)勾缭,那么后期綁定這個(gè)概念,又是如何實(shí)現(xiàn)的呢目养?
其實(shí)就是這里的動態(tài)解析俩由,如果A調(diào)用的B是一個(gè)具體的實(shí)現(xiàn)類,那么就稱為靜態(tài)解析癌蚁,因?yàn)榻忉尩哪繕?biāo)類型很明確幻梯,而假如上層Java代碼使用了多態(tài)兜畸,這里的B是一個(gè)抽象類或者是接口,那么 B可能有兩個(gè)具體的實(shí)現(xiàn)類C和D 此時(shí)碘梢,B的具體實(shí)現(xiàn)并不明確咬摇,當(dāng)然也就不知道使用哪個(gè)具體類的直接引用來進(jìn)行替換,既然不知道煞躬,那么就等一等吧肛鹏,直到運(yùn)行過程中發(fā)生了調(diào)用,此時(shí)虛擬機(jī)調(diào)用棧中將會得到具體的類型信息恩沛,這時(shí)候再進(jìn)行解析在扰,就能用明確的直接引用來替換符號引用,這就解釋了為什么解析階段有時(shí)候會發(fā)生在初始化階段之后复唤,這就是動態(tài)解析健田,用它來實(shí)現(xiàn)了后期綁定。底層對應(yīng)了invokedynamic這條字節(jié)碼指令佛纫。
擴(kuò)展:多態(tài)
擴(kuò)展:動態(tài)綁定
初始化
初始化階段簡單概括:此時(shí)會判斷代碼中是否存在主動的資源初始化操作,如果有的話总放,那么執(zhí)行呈宇。這里所說的主動的資源初始化動作,不是指的構(gòu)造函數(shù)局雄,而是class層面的甥啄,比如說成員變量的賦值動作,靜態(tài)變量的賦值動作炬搭,以及靜態(tài)代碼塊的邏輯蜈漓。而只有顯式的調(diào)用new指令 才會調(diào)用構(gòu)造函數(shù)進(jìn)行對象的實(shí)例化,這是對象層面的宫盔,二者不能進(jìn)行混淆融虽。