淺析Java虛擬機結構與機制 (上)
- 作者:新網(wǎng)
- 來源:新網(wǎng)
- 瀏覽:100
- 2018-05-11 15:15:39
本文主要介紹JVM的組成部分以及它們內部工作的機制和原理。在研究JVM的過程中會發(fā)現(xiàn),其實JVM本身就是一個計算機體系結構,很多原理和我們平時的硬件、微機原理、 操作系統(tǒng)都有十分相似的地方,所以學習JVM本身也是加深自我對計算機結構認識的一個很好的途徑。
本文主要介紹JVM的組成部分以及它們內部工作的機制和原理。在研究JVM的過程中會發(fā)現(xiàn),其實JVM本身就是一個計算機體系結構,很多原理和我們平時的硬件、微機原理、 操作系統(tǒng)都有十分相似的地方,所以學習JVM本身也是加深自我對計算機結構認識的一個很好的途徑。
<
div> 一、JVM結構
JVM主要由類加載器子系統(tǒng)、運行時數(shù)據(jù)區(qū)(內存
空間)、執(zhí)行引擎以及與本地方法接口等組成。其中運行時數(shù)據(jù)區(qū)又由方法區(qū)、堆、Java棧、PC寄存器、本地方法棧組成。JVM主要由類加載器子系統(tǒng)、運行時數(shù)據(jù)區(qū)(內存空間)、執(zhí)行引擎以及與本地方法接口等組成。其中運行時數(shù)據(jù)區(qū)又由方法區(qū)、堆、Java棧、PC寄存器、本地方法棧組成。
眾所周知,Java語言具有跨平臺的特性,這也是由JVM來實現(xiàn)的。更準確地說,是Sun利用JVM在不同平臺上的實現(xiàn)幫我們把平臺相關性的問題給 解決了,這就好比是HTML語言可以在不同廠商的瀏覽器上呈現(xiàn)元素(雖然某些瀏覽器在對W3C標準的支持上還有一些問題)。同時,Java語言支持通過 JNI(Java Native Interface)來實現(xiàn)本地方法的調用,但是需要注意到,如果你在Java程序用調用了本地方法,那么你的程序就很可能不再具有跨平臺性,即本地方法 會破壞平臺無關性。
二、類加載器子系統(tǒng)(Class Loader)
類加載器子系統(tǒng)負責加載編譯好的.class字節(jié)碼文件,并裝入內存,使JVM可以實例化或以其它方式使用加載后的類。 JVM的類加載子系統(tǒng)支持在運行時的動態(tài)加載,動態(tài)加載的優(yōu)點有很多,例如可以節(jié)省內存空間、靈活地從網(wǎng)絡上加載類,動態(tài)加載的另一好處是可以通過命名空 間的分隔來實現(xiàn)類的隔離,增強了整個系統(tǒng)的安全性。
1、ClassLoader的分類:
a.啟動類加載器(BootStrap Class Loader):負責加載rt.jar文件中所有的Java類,即Java的核心類都是由該ClassLoader加載。在Sun JDK中,這個類加載器是由C++實現(xiàn)的,并且在Java語言中無法獲得它的引用。
b.擴展類加載器(Extension Class Loader):負責加載一些擴展功能的jar包。
c.系統(tǒng)類加載器(System Class Loader):負責加載啟動參數(shù)中指定的Classpath中的jar包及目錄,通常我們自己寫的Java類也是由該ClassLoader加載。在Sun JDK中,系統(tǒng)類加載器的名字叫AppClassLoader。
d.用戶自定義類加載器(User Defined Class Loader):由用戶自定義類的加載規(guī)則,可以手動控制加載過程中的步驟。
2、ClassLoader的工作原理
類加載分為裝載、鏈接、初始化三步。
a.裝載
通過類的全限定名和ClassLoader加載類,主要是將指定的.class文件加載至JVM。當類被加載以后,在JVM內部就以“類的全限定名+ClassLoader實例ID”來標明類。
在內存中,ClassLoader實例和類的實例都位于堆中,它們的類信息都位于方法區(qū)。
裝載過程采用了一種被稱為“雙親委派模型(Parent Delegation Model)” 的方式,當一個ClassLoader要加載類時,它會先請求它的雙親ClassLoader(其實這里只有兩個ClassLoader,所以稱為父 ClassLoader可能更容易理解)加載類,而它的雙親ClassLoader會繼續(xù)把加載請求提交再上一級的ClassLoader,直到啟動類加 載器。只有其雙親ClassLoader無法加載指定的類時,它才會自己加載類。
雙親委派模型是JVM的第一道安全防線,它保證了類的安全加載,這里同時依賴了類加載器隔離的原理:不同類加載器加載的類之間是無法直接交互的,即 使是同一個類,被不同的ClassLoader加載,它們也無法感知到彼此的存在。這樣即使有惡意的類冒充自己在核心包(例如
java.lang)下,由 于它無法被啟動類加載器加載,也造成不了危害。
由此也可見,如果用戶自定義了類加載器,那就必須自己保障類加載過程中的安全。
b.鏈接
鏈接的任務是把二進制的類型信息合并到JVM運行時狀態(tài)中去。
鏈接分為以下三步:
a.驗證:校驗.class文件的正確性,確保該文件是符合規(guī)范定義的,并且適合當前JVM使用。
b.準備:為類分配內存,同時初始化類中的靜態(tài)變量賦值為默認值。
c.解析(可選):主要是把類的常量池中的符號引用解析為直接引用,這一步可以在用到相應的引用時再解析。
c.初始化
初始化類中的靜態(tài)變量,并執(zhí)行類中的static代碼、構造函數(shù)。
JVM規(guī)范嚴格定義了何時需要對類進行初始化:
a、通過new關鍵字、反射、clone、反序列化機制實例化對象時。
b、調用類的靜態(tài)方法時。
c、使用類的靜態(tài)字段或對其賦值時。
d、通過反射調用類的方法時。
e、初始化該類的子類時(初始化子類前其父類必須已經(jīng)被初始化)。
f、JVM啟動時被標記為啟動類的類(簡單理解為具有main方法的類)。
三、Java棧(Java Stack)
Java棧由棧幀組成,一個幀對應一個方法調用。調用方法時壓入棧幀,方法返回時彈出棧幀并拋棄。Java棧的主要任務是 存儲方法參數(shù)、局部變量、中間運算結果,并且提供部分其它模塊工作需要的數(shù)據(jù)。前面已經(jīng)提到Java棧是線程私有的,這就保證了線程安全性,使得程序員無 需考慮棧同步訪問的問題,只有線程本身可以訪問它自己的局部變量區(qū)。
它分為三部分:局部變量區(qū)、操作數(shù)棧、幀數(shù)據(jù)區(qū)。
1、局部變量區(qū)
局部變量區(qū)是以字長為單位的數(shù)組,在這里,byte、short、char類型會被轉換成int類型存儲,除了long和 double類型占兩個字長以外,其余類型都只占用一個字長。特別地,boolean類型在編譯時會被轉換成int或byte類型,boolean數(shù)組會 被當做byte類型數(shù)組來處理。局部變量區(qū)也會包含對象的引用,包括類引用、接口引用以及數(shù)組引用。
局部變量區(qū)包含了方法參數(shù)和局部變量,此外,實例方法隱含第一個局部變量this,它指向調用該方法的對象引用。對于對象,局部變量區(qū)中永遠只有指向堆的引用。
2、操作數(shù)棧
操作數(shù)棧也是以字長為單位的數(shù)組,但是正如其名,它只能進行入棧出棧的基本操作。在進行計算時,操作數(shù)被彈出棧,計算完畢后再入棧。
3、幀數(shù)據(jù)區(qū)
幀數(shù)據(jù)區(qū)的任務主要有:
a.記錄指向類的常量池的指針,以便于解析。
b.幫助方法的正常返回,包括恢復調用該方法的棧幀,設置PC寄存器指向調用方法對應的下一條指令,把返回值壓入調用棧幀的操作數(shù)棧中。
c.記錄異常表,發(fā)生異常時將控制權交由對應異常的catch子句,如果沒有找到對應的catch子句,會恢復調用方法的棧幀并重新拋出異常。
局部變量區(qū)和操作數(shù)棧的大小依照具體方法在編譯時就已經(jīng)確定。調用方法時會從方法區(qū)中找到對應類的類型信息,從中得到具體方法的局部變量區(qū)和操作數(shù)棧的大小,依此分配棧幀內存,壓入Java棧。