淺析Java虛擬機(jī)結(jié)構(gòu)與機(jī)制 (下)
- 作者:新網(wǎng)
- 來(lái)源:新網(wǎng)
- 瀏覽:100
- 2018-05-11 15:13:46
本文主要介紹JVM的組成部分以及它們內(nèi)部工作的機(jī)制和原理。在研究JVM的過(guò)程中會(huì)發(fā)現(xiàn),其實(shí)JVM本身就是一個(gè)計(jì)算機(jī)體系結(jié)構(gòu),很多原理和我們平時(shí)的硬件、微機(jī)原理、 操作系統(tǒng)都有十分相似的地方,所以學(xué)習(xí)JVM本身也是加深自我對(duì)計(jì)算機(jī)結(jié)構(gòu)認(rèn)識(shí)的一個(gè)很好的途徑。
本文主要介紹JVM的組成部分以及它們內(nèi)部工作的機(jī)制和原理。在研究JVM的過(guò)程中會(huì)發(fā)現(xiàn),其實(shí)JVM本身就是一個(gè)計(jì)算機(jī)體系結(jié)構(gòu),很多原理和我們平時(shí)的硬件、微機(jī)原理、 操作系統(tǒng)都有十分相似的地方,所以學(xué)習(xí)JVM本身也是加深自我對(duì)計(jì)算機(jī)結(jié)構(gòu)認(rèn)識(shí)的一個(gè)很好的途徑。
<
div> 四、本地方法棧(Native Method Stack)
本地方法棧類(lèi)似于Java棧,主要存儲(chǔ)了本地方法調(diào)用的狀態(tài)。在Sun JDK中,本地方法棧和Java棧是同一個(gè)。
五、方法區(qū)(Method Area)
類(lèi)型信息和類(lèi)的靜態(tài)變量都存儲(chǔ)在方法區(qū)中。方法區(qū)中對(duì)于每個(gè)類(lèi)存儲(chǔ)了以下數(shù)據(jù):
a.類(lèi)及其父類(lèi)的全限定名(
java.lang.Object沒(méi)有父類(lèi))
b.類(lèi)的類(lèi)型(Class or Interface)
c.訪(fǎng)問(wèn)修飾符(public, abstract, final)
d.實(shí)現(xiàn)的接口的全限定名的列表
e.常量池
f.字段信息
g.方法信息
h.靜態(tài)變量
i.ClassLoader引用
j.Class引用
可見(jiàn)類(lèi)的所有信息都存儲(chǔ)在方法區(qū)中。由于方法區(qū)是所有線(xiàn)程共享的,所以必須保證線(xiàn)程安全,舉例來(lái)說(shuō),如果兩個(gè)類(lèi)同時(shí)要加載一個(gè)尚未被加載的類(lèi),那么一個(gè)類(lèi)會(huì)請(qǐng)求它的ClassLoader去加載需要的類(lèi),另一個(gè)類(lèi)只能等待而不會(huì)重復(fù)加載。
此外為了加快調(diào)用方法的速度,通常還會(huì)為每個(gè)非抽象類(lèi)創(chuàng)建私有的方法表,方法表是一個(gè)數(shù)組,存放了實(shí)例可能被調(diào)用的實(shí)例方法的直接引用。方法表對(duì)于多態(tài)有非常重要的意義,具體可以參照《淺談多態(tài)機(jī)制的意義及實(shí)現(xiàn)》一文中“多態(tài)的實(shí)現(xiàn)”一節(jié)。
在Sun JDK中,方法區(qū)對(duì)應(yīng)了持久代(Permanent Generation),默認(rèn)最小值為16MB,最大值為64MB。
六、堆(Heap)
堆用于存儲(chǔ)對(duì)象實(shí)例以及數(shù)組值。堆中有指向類(lèi)數(shù)據(jù)的指針,該指針指向了方法區(qū)中對(duì)應(yīng)的類(lèi)型信息。堆中還可能存放了指向方法 表的指針。堆是所有線(xiàn)程共享的,所以在進(jìn)行實(shí)例化對(duì)象等操作時(shí),需要解決同步問(wèn)題。此外,堆中的實(shí)例數(shù)據(jù)中還包含了對(duì)象鎖,并且針對(duì)不同的垃圾收集策略, 可能存放了引用計(jì)數(shù)或清掃標(biāo)記等數(shù)據(jù)。
在堆的管理上,Sun JDK從1.2版本開(kāi)始引入了分代管理的方式。主要分為新生代、舊生代。分代方式大大改善了垃圾收集的效率。
1、新生代(New Generation)
大多數(shù)情況下新對(duì)象都被分配在新生代中,新生代由Eden Space和兩塊相同大小的Survivor Space組成,后兩者主要用于Minor GC時(shí)的對(duì)象復(fù)制(Minor GC的過(guò)程在此不詳細(xì)討論)。
JVM在Eden Space中會(huì)開(kāi)辟一小塊獨(dú)立的TLAB(Thread Local Allocation Buffer)區(qū)域用于更高效的內(nèi)存分配,我們知道在堆上分配內(nèi)存需要鎖定整個(gè)堆,而在TLAB上則不需要,JVM在分配對(duì)象時(shí)會(huì)盡量在TLAB上分配,以提高效率。
2、舊生代(Old Generation/Tenuring Generation)
在新生代中存活時(shí)間較久的對(duì)象將會(huì)被轉(zhuǎn)入舊生代,舊生代進(jìn)行垃圾收集的頻率沒(méi)有新生代高。
七、執(zhí)行引擎
執(zhí)行引擎是JVM執(zhí)行Java字節(jié)碼的核心,執(zhí)行方式主要分為解釋執(zhí)行、編譯執(zhí)行、自適應(yīng)優(yōu)化執(zhí)行、硬件芯片執(zhí)行方式。
JVM的指令集是基于棧而非寄存器的,這樣做的好處在于可以使指令盡可能緊湊,便于快速地在網(wǎng)絡(luò)上傳輸(別忘了Java最初就是為網(wǎng)絡(luò)設(shè)計(jì)的),同 時(shí)也很容易適應(yīng)通用寄存器較少的平臺(tái),并且有利于代碼優(yōu)化,由于Java棧和PC寄存器是線(xiàn)程私有的,線(xiàn)程之間無(wú)法互相干涉彼此的棧。每個(gè)線(xiàn)程擁有獨(dú)立的 JVM執(zhí)行引擎實(shí)例。
JVM指令由單字節(jié)操作碼和若干操作數(shù)組成。對(duì)于需要操作數(shù)的指令,通常是先把操作數(shù)壓入操作數(shù)棧,即使是對(duì)局部變量賦值,也會(huì)先入棧再賦值。注意這里是“通常”情況,之后會(huì)講到由于優(yōu)化導(dǎo)致的例外。
1、解釋執(zhí)行
和一些動(dòng)態(tài)語(yǔ)言類(lèi)似,JVM可以解釋執(zhí)行字節(jié)碼。Sun JDK采用了token-threading的方式,感興趣的同學(xué)可以深入了解一下。
解釋執(zhí)行中有幾種優(yōu)化方式:
a.棧頂緩存
將位于操作數(shù)棧頂?shù)闹抵苯泳彺嬖诩拇嫫魃?,?duì)于大部分只需要一個(gè)操作數(shù)的指令而言,就無(wú)需再入棧,可以直接在寄存器上進(jìn)行計(jì)算,結(jié)果壓入操作數(shù)站。這樣便減少了寄存器和內(nèi)存的交換開(kāi)銷(xiāo)。
b.部分棧幀共享
被調(diào)用方法可將調(diào)用方法棧幀中的操作數(shù)棧作為自己的局部變量區(qū),這樣在獲取方法參數(shù)時(shí)減少了復(fù)制參數(shù)的開(kāi)銷(xiāo)。
c.執(zhí)行機(jī)器指令
在一些特殊情況下,JVM會(huì)執(zhí)行機(jī)器指令以提高速度。
2、編譯執(zhí)行
為了提升執(zhí)行速度,Sun JDK提供了將字節(jié)碼編譯為機(jī)器指令的支持,主要利用了JIT(Just-In-Time)編譯器在運(yùn)行時(shí)進(jìn)行編譯,它會(huì)在第一次執(zhí)行時(shí)編譯字節(jié)碼為機(jī)器碼并緩存,之后就可以重復(fù)利用。Oracle JRockit采用的是完全的編譯執(zhí)行。
3、自適應(yīng)優(yōu)化執(zhí)行
自適應(yīng)優(yōu)化執(zhí)行的思想是程序中10%~20%的代碼占據(jù)了80%~90%的執(zhí)行時(shí)間,所以通過(guò)將那少部分代碼編譯為優(yōu)化過(guò) 的機(jī)器碼就可以大大提升執(zhí)行效率。自適應(yīng)優(yōu)化的典型代表是Sun的Hotspot VM,正如其名,JVM會(huì)監(jiān)測(cè)代碼的執(zhí)行情況,當(dāng)判斷特定方法是瓶頸或熱點(diǎn)時(shí),將會(huì)啟動(dòng)一個(gè)后臺(tái)線(xiàn)程,把該方法的字節(jié)碼編譯為極度優(yōu)化的、靜態(tài)鏈接的 C++代碼。當(dāng)方法不再是熱區(qū)時(shí),則會(huì)取消編譯過(guò)的代碼,重新進(jìn)行解釋執(zhí)行。
自適應(yīng)優(yōu)化不僅通過(guò)利用小部分的編譯時(shí)間獲得大部分的效率提升,而且由于在執(zhí)行過(guò)程中時(shí)刻監(jiān)測(cè),對(duì)內(nèi)聯(lián)代碼等優(yōu)化也起到了很大的作用。由于面向?qū)ο蟮亩鄳B(tài)性,一個(gè)方法可能對(duì)應(yīng)了很多種不同實(shí)現(xiàn),自適應(yīng)優(yōu)化就可以通過(guò)監(jiān)測(cè)只內(nèi)聯(lián)那些用到的代碼,大大減少了內(nèi)聯(lián)函數(shù)的大小。
Sun JDK在編譯上采用了兩種模式:Client和Server模式。前者較為輕量級(jí),占用內(nèi)存較少。后者的優(yōu)化程序更高,占用內(nèi)存更多。
在Server模式中會(huì)進(jìn)行對(duì)象的逃逸分析,即方法中的對(duì)象是否會(huì)在方法外使用,如果被其它方法使用了,則該對(duì)象是逃逸的。對(duì)于非逃逸對(duì)象,JVM 會(huì)在棧上直接分配對(duì)象(所以對(duì)象不一定是在堆上分配的),線(xiàn)程獲取對(duì)象會(huì)更加快速,同時(shí)當(dāng)方法返回時(shí),由于棧幀被拋棄,也有利于對(duì)象的垃圾收集。 Server模式還會(huì)通過(guò)分析去除一些不必要的同步,感興趣的同學(xué)可以研究一下Sun JDK 6引入的Biased Locking機(jī)制。
此外,執(zhí)行引擎也必須保證線(xiàn)程安全性,因而JMM(Java Memory Model)也是由執(zhí)行引擎確保的。