類的生命周期概述
Java程序的所有數(shù)據(jù)結(jié)構(gòu)和算法都封裝在類型之中,這也是面向?qū)ο缶幊陶Z(yǔ)言的一大特色。當(dāng)JVM執(zhí)行一個(gè)Java類所封裝的算法之前 ,首先要做的一件事便是字節(jié)碼文件解析,字節(jié)碼文件解析包含 3 個(gè)主要的過(guò)程:常量池解析、Java類字段解析及 Java 方法解析。通過(guò)類字段解析,JVM能夠分析出Java類所封裝的數(shù)據(jù)結(jié)構(gòu);通過(guò)方法解析,JVM能夠分析出Java類所封裝的算法邏輯 。而無(wú)論是數(shù)據(jù)結(jié)構(gòu)還是方法信息,很多與“字符串”或者大數(shù)據(jù)(是指占二進(jìn)位比較多的大數(shù))相關(guān)的信息都封裝于常量池中,所以JVM欲解析字段和方法信息,必先解析常量池。當(dāng)常量池、字段和方法信息全部被解析完,則字節(jié)碼文件的“精華”便已經(jīng)被完全消化吸收。但是,這幾個(gè)過(guò)程其實(shí)僅僅屬于Java類 “加載”過(guò)程中的一個(gè)環(huán)節(jié),這對(duì)于一個(gè)Java類的整個(gè)“漫長(zhǎng)”的生命周期而言,僅僅是個(gè)開始。在字節(jié)碼文件的精華被吸收之后還需要經(jīng)過(guò)一系列的“二次” 消化處理,方能被JVM在運(yùn)行期“隨心所欲”地調(diào)用。
按照J(rèn)VM規(guī)范,一個(gè)Java文件從被加載到被卸載的整個(gè)生命過(guò)程,總共要經(jīng)歷5個(gè)階段 : 加載?→?鏈接(驗(yàn)證+準(zhǔn)備+解析)→?初始化(使用前的準(zhǔn)備)→?使用?→?卸載。其中第二個(gè)階段“鏈接”,對(duì)應(yīng)了3個(gè)階段 :驗(yàn)證 、準(zhǔn)備和解析,因此,也有很多典籍說(shuō) Java 類的生命周期一共包括7個(gè)階段。
前文所講的常量池解析、Java字段和方法的解析,其實(shí)都屬于加載階段的一部分。所謂加 載,簡(jiǎn)而言之就是將 Java 類的字節(jié)碼文件加載到機(jī)器內(nèi)存中并在內(nèi)存中構(gòu)建出Java類的原型類模板對(duì)象。所謂類模板對(duì)象,其實(shí)就是 Java類在JVM內(nèi)存中的一個(gè)快照,JVM將從字節(jié)碼文件中解析出的常量池、類字段、類方法等信息存儲(chǔ)到類模板中,這樣JVM在運(yùn)行期便能通過(guò)類模板而獲取Java類中的任意信息,能夠?qū)?Java 類的成員變量進(jìn)行遍歷,也能進(jìn)行 Java 方法的調(diào)用。反射的機(jī)制即基于這一基礎(chǔ)。如果 JVM 沒(méi)有將 Java 類的聲明信息存儲(chǔ)起來(lái),則只叫在運(yùn)行期也無(wú)法反射。字節(jié)碼相關(guān)的工具類庫(kù),例如 asm 、cglib 等,都利用了這一機(jī)制,在運(yùn)行期動(dòng)態(tài)修改靜態(tài)聲明的 Java 類所對(duì)應(yīng)的字節(jié)碼內(nèi)容,從而在運(yùn)行期直接改掉Java 類的定義,甚至直接在運(yùn)行期創(chuàng)建一個(gè)全新的Java類。
Java類是寫給人類看的,而只叫內(nèi)存中的類模板快照則是寫給機(jī)器“看”的。物理機(jī)器無(wú)法直接執(zhí)行Java類的源代碼,所以需要通過(guò)類加載這樣一個(gè)過(guò)程將字節(jié)碼格式的 Java 類轉(zhuǎn)換成機(jī)器能夠識(shí)別的內(nèi)存類模板快照。
JVM完成 Java 類加載之后,接著便開始進(jìn)行鏈接。所謂鏈接,雖然與編譯原理中的鏈接不是同一件事,然而本質(zhì)上是相同的??傮w而言,鏈接的主要作用是將字節(jié)碼指令中對(duì)常量池中的索引引用轉(zhuǎn)換為直接引用。鏈接包含 3 個(gè)步驟 :驗(yàn)證、準(zhǔn)備和解析。其實(shí)在類加載階段(也即類的生命周期的第一個(gè)階段)JVM會(huì)對(duì)字節(jié)碼文件進(jìn)行驗(yàn)證,只不過(guò)該階段的驗(yàn)證著重于字節(jié)碼文件格式本身,與“鏈接”階段的驗(yàn)證側(cè)重點(diǎn)不同。在鏈接階段,著重于由字節(jié)碼信息出發(fā)進(jìn)行反向驗(yàn)證,例如,驗(yàn)證根據(jù)字節(jié)碼文件中的類名是否能夠找到對(duì)應(yīng)的類模板。這些都驗(yàn)證無(wú)誤之后,JVM才能放心地加載當(dāng)前類,也才能放心地將字節(jié)碼指令中對(duì)常量池索引號(hào)的引用重寫為直接引用。
在正式使用Java類之前的最后一道工序便是“初始化”,這里所謂的初始化,并非指對(duì)類進(jìn)行實(shí)例化,而是指執(zhí)行類的()方法。Java類的實(shí)例化,對(duì)應(yīng)的乃是 Java 類 生命周期中的”使用”段??傮w而言,當(dāng)Java類中包含 static 修飾的靜態(tài)字段 ,或者有使用 static{}塊包裹的代碼段時(shí),編譯后便會(huì)在字節(jié)碼文件中包含一個(gè)名為()的方法,JVM在初始化階段便會(huì)調(diào)用該方法。需要說(shuō)明一點(diǎn),該方法僅能由Java編譯器生成并由JVM調(diào)用,程序開發(fā)者無(wú)法自定義一個(gè)同名的方法,更無(wú)法直接在Java程序中調(diào)用該方法 雖,該方法也是由字節(jié)碼指令所組成。
等JVM完成類的初始化之后,便“萬(wàn)事俱備,只欠東風(fēng)”,就等著開發(fā)者使用了。使用方式多種多樣,其中最常見的一種方式是通過(guò)new關(guān)鍵字來(lái)實(shí)例化一個(gè) Java 類。
當(dāng)然,除了通過(guò)new關(guān)鍵字使用java類,還有多種方式,例如下面的例子:

該示例使用Class.forName(String)接口加載一個(gè)類,并通過(guò)Class.newlnstance()接口實(shí)例化一個(gè)類。
從廣義上說(shuō),類的加載也可以對(duì)應(yīng)類生命周期的7個(gè)階段中的前 5個(gè)階段即加載、驗(yàn)證 、 準(zhǔn)備 、解析和初始化。當(dāng)類加載之后,JVM內(nèi)部會(huì)為Java類創(chuàng)建一個(gè)對(duì)等的類模板,類模板在JDK 6時(shí)代被存儲(chǔ)在所謂的perm區(qū),而到了JDK 8時(shí)代,則被存儲(chǔ)在所謂的metaSpace 區(qū)。無(wú)論存儲(chǔ)在哪里,當(dāng)存儲(chǔ)區(qū)即將被打爆而這個(gè)類又不再使用時(shí),JVM的GC便有可能將其回收,即釋放內(nèi)存。而當(dāng)實(shí)例化一個(gè) Java 類之后 ,JVM內(nèi)部則會(huì)為Java類實(shí)例對(duì)象創(chuàng)建一個(gè)對(duì)等的實(shí)例對(duì)象,該實(shí)例對(duì)象所存儲(chǔ)的區(qū)域與具體的 GC 策略緊密關(guān)聯(lián),有可能在新生代,也可能在老年代,當(dāng)然,更可能在棧上(棧上分配)。當(dāng)類被使用完畢之后,JVM必須銷毀實(shí)例對(duì)象,否則只怕內(nèi)存區(qū)早晚會(huì)被打爆。JVM對(duì)類模板的銷毀和類實(shí)例對(duì)象的銷毀,都是卸載。
總體而言,Java類的生命周期如圖所示。

類加載
前文已經(jīng)描述過(guò)JVM對(duì)字節(jié)碼文件的精華部分的解析過(guò)程,當(dāng)字節(jié)碼文件解析完成之后,JVM便會(huì)在內(nèi)部創(chuàng)建一個(gè)與Java類對(duì)等的類模板對(duì)象,說(shuō)白了該對(duì)象其實(shí)是 C++類的實(shí)例。每一個(gè)Java類模型,最終在只叫內(nèi)部都會(huì)有一個(gè) klassOop 與之對(duì)等 ,Java 類中的字段 、方法及 常量池等都會(huì)保存到 klassOop 實(shí)例對(duì)象中。要注意,這個(gè)實(shí)例對(duì)象并非 Java 類的實(shí)例對(duì)象,其僅僅用于表示 Java 類型本身,或者 Java 類的定義。與 Java 類實(shí)例對(duì)象對(duì)等的JVM內(nèi)部對(duì)象是instanceOop實(shí)例。
下面就從Java類模板對(duì)象instanceKlass 的創(chuàng)建開始講起。
前面描述過(guò)Java字節(jié)碼文件的常量池解析 、字段解析與方法解析 ,這三部分內(nèi)容的解析便是 Java 字節(jié)碼文件的精華所在。當(dāng)這 3 個(gè)過(guò)程執(zhí)行完成之后,Java字節(jié)碼文件的精華便被分析完了,至此JVM便對(duì)Java類中所定義的一切數(shù)據(jù)結(jié)構(gòu)和算法“了如指掌”,為了鞏固“勝利成果”,JVM需要將這些好不容易辛辛苦苦解析出來(lái)的結(jié)果保存起來(lái)。這些解析的結(jié)果會(huì)存儲(chǔ)到klassOop這個(gè)內(nèi)部類對(duì)象實(shí)例中,可以將該對(duì)象看作 Java 類在JVM內(nèi)部完全對(duì)等的一個(gè)鏡像,只不過(guò)Java類是寫給人類看的,而內(nèi)部鏡像 klassOop 則是寫給機(jī)器讀的。當(dāng)成功保存解析結(jié)果之后,則 Java 類的生命周期的第一個(gè)階段加載,便大功告成。不看過(guò)程看結(jié)果,類加載階段其實(shí)就是為了這一目標(biāo)而來(lái),在JVM內(nèi)部創(chuàng)建一個(gè)與Java類結(jié)構(gòu)對(duì)等的數(shù)據(jù)對(duì)象。


從instanceKlass的結(jié)構(gòu)可以看到,其內(nèi)部定義了若干字段,這些字段足以存儲(chǔ) Java 類規(guī)范所支持的一切信息,例如字段 、方法 、內(nèi)部類等,因?yàn)?instanceKlass 要作為 Java 類在JVM內(nèi)部對(duì)等的結(jié)構(gòu)體,所以能夠兼容Java類中的所有元素是其唯一的設(shè)計(jì)目標(biāo)。但是 JVM在創(chuàng)建instanceKlass對(duì)象時(shí),為其所申請(qǐng)的內(nèi)存空間卻超過(guò)了instanceKlass 類型本身所需的內(nèi)存大小,這是因?yàn)镴VM需要在instanceKlass內(nèi)存空間的末尾再預(yù)留出足夠的空間,存儲(chǔ)虛方法表 vtable接口表 itable 及 JAVA 類中的引用類型表 oopMap。存儲(chǔ)虛方法表 vtable,其作用在前文分析Java
方法的解析機(jī)制時(shí)詳細(xì)描述過(guò),這里不再贅述。itable與 oopMap 也是各有其作用。不過(guò)靜態(tài)字段在不同的 JDK 版本中存放的位置不同,在JDK 6中,靜態(tài)字段會(huì)被分配到 instanceKlass 實(shí)例對(duì)象所申請(qǐng)的內(nèi)存空間中,而在 JDK7和JDK8中,靜態(tài)字段將會(huì)被分配到與 instanceKlass對(duì)等的鏡像類一 java .lang.Class 實(shí)例中,關(guān)于靜態(tài)字段的分配及鏡像類會(huì)在下文詳細(xì)分析,此處先略過(guò)不表。
圖中的這個(gè)內(nèi)存數(shù)據(jù)結(jié)構(gòu),便是Java類加載的最終產(chǎn)物,也是Java類在內(nèi)存中的對(duì)等體。JVM根據(jù)這個(gè)數(shù)據(jù)結(jié)構(gòu),能夠獲取Java類中所定義的一切元素。

類加載的最終結(jié)果便是在JVM的方法區(qū)創(chuàng)建一個(gè)與Java類對(duì)等的instanceKlass 實(shí)例對(duì)象,但是在JVM創(chuàng)建完instanceKlass之后,又創(chuàng)建了與之對(duì)等的另一個(gè)鏡像類java.lang.Class。
JVM之所以在instanceKlass之外再創(chuàng)建一個(gè)mirror,是有用意的,總體而言,java.lang. Class是為了被 Java 程序調(diào)用,而 instanceKlass 則是為了被JVM內(nèi)部訪問(wèn)。所以,JVM直接暴露給Java的是 java_mirror,而不是 InstanceKlass 。
JDK8之所以將靜態(tài)字段從instanceKlass 遷移到mirror中,也不是沒(méi)有道理。畢竟靜態(tài)字段并非Java類的成員變量,如果從數(shù)據(jù)結(jié)構(gòu)這個(gè)角度看,靜態(tài)字段不能算作 Java 類這個(gè)數(shù)據(jù)結(jié)構(gòu)的一部分,因此 JDK 8 將靜態(tài)字段轉(zhuǎn)移到 mirror 中。從反射的角度看,靜態(tài)字段放在 mirror 中是合理的,畢竟在進(jìn)行反射時(shí),需要給 出 Java 類中所定義的全部字段,無(wú)論字段是不是靜態(tài)類型。