Java虛擬機(jī)體內(nèi)部系結(jié)構(gòu)包括class文件、類裝載子系統(tǒng)、運(yùn)行時(shí)數(shù)據(jù)區(qū)、之行引擎、本地方法調(diào)用結(jié)構(gòu),其中運(yùn)行時(shí)數(shù)據(jù)區(qū)包括方法區(qū)、堆、Java棧、程序計(jì)數(shù)器、本地方法棧等。具體結(jié)構(gòu)如下圖所示(摘自Inside Java Virtual Machine):

1. class文件
在Java中,所有源文件都編譯成二進(jìn)制的字節(jié)碼,然后由虛擬機(jī)裝載運(yùn)行。一般這樣的字節(jié)碼是以class文件的形式存在。在運(yùn)行時(shí),由ClassLoader類(System ClassLoader or User-defined ClassLoader)找到對(duì)應(yīng)的class文件,讀取其中的字節(jié)碼,然后交由虛擬機(jī)解析運(yùn)行。
在class文件中,包含了定義一個(gè)類或接口的所有信息,包括類名、訪問(wèn)權(quán)限、父類名、繼承的所有接口、所有字段、所有方法、方法中的代碼、屬性等信息,并且每個(gè)class文件的開(kāi)頭還包含了魔術(shù)值和版本信息,魔術(shù)值用以標(biāo)識(shí)當(dāng)前的字節(jié)碼是合法的字節(jié)碼,版本表示生成當(dāng)前字節(jié)碼的編譯器版本,從而虛擬機(jī)獲知其版本而做特定處理,如果對(duì)于虛擬機(jī)不支持的字節(jié)碼版本號(hào)拒絕加載。
在class文件中,很多信息都是以字符串的形式存放,比如對(duì)外部類成員或方法的引用,這些字符串信息在鏈接的時(shí)候由虛擬機(jī)解析。每個(gè)Java類,不管是包成員類還是內(nèi)部類都會(huì)生成一個(gè)單獨(dú)的class文件,因而class文件是相對(duì)獨(dú)立的。詳細(xì)信息參考class文件格式。
2. 類裝載子系統(tǒng)
類裝載子系統(tǒng)負(fù)責(zé)查找class文件,讀取字節(jié)碼,做部分簡(jiǎn)單的檢驗(yàn),如魔數(shù)是否正確,版本是否受支持,各種數(shù)據(jù)格式是否正確等。部分解析后的字節(jié)碼數(shù)據(jù)存放到方法區(qū)中,最后創(chuàng)建字節(jié)碼代表的類或接口的Class實(shí)例。
在Java中,類裝載系統(tǒng)是通過(guò)ClassLoader來(lái)完成的。虛擬機(jī)規(guī)范中,定義了啟動(dòng)類裝載器和用于定義類裝載器。在sun提供的虛擬機(jī)中,包括了啟動(dòng)類裝載器、擴(kuò)展類裝載器、系統(tǒng)類裝載器、用戶定義類裝載器。他們以父子鏈的方式組織在一起。除了啟動(dòng)類裝載器,其他的裝載器都是ClassLoader的子類。ClassLoader定義了一些方法可以幫助用戶定義自己的類裝載器,如defineClass等。詳情參考Java中的ClassLoader。
如何卸載類數(shù)據(jù)?(第七章)
3. 運(yùn)行時(shí)數(shù)據(jù)區(qū)
運(yùn)行時(shí)數(shù)據(jù)區(qū)保存了所有在運(yùn)行時(shí)的信息。包括方法區(qū)、Java棧、堆、程序寄存器、本地方法棧等。其中方法區(qū)和堆只在虛擬機(jī)中保存一份實(shí)例,因而需要處理多線程的同步問(wèn)題;Java棧、程序寄存器是每個(gè)線程中有單獨(dú)的實(shí)例,因而對(duì)不同的線程,他們的數(shù)據(jù)是私有的。
3.1 方法區(qū)
方法區(qū)中保存了讀取的字節(jié)碼信息(包括常量池,靜態(tài)方法和靜態(tài)成員信息)、字節(jié)碼代表的Class類實(shí)例、一個(gè)指向加載它的ClassLoader實(shí)例。
Java程序可以有兩種方式來(lái)獲取某個(gè)類的Class實(shí)例:
1. Class.forName()方法
2. Object.getClass()方法
通過(guò)Class實(shí)例獲取和該類或接口相關(guān)的任何信息。參考Class類的定義。
(注:對(duì)有啟動(dòng)ClassLoader加載的類,Class方法中的getClassLoader方法返回null)
為加快執(zhí)行速度,可以在方法區(qū)中引入方法表機(jī)制,記錄能被外界調(diào)用的該類的實(shí)例方法,包括父類中繼承下來(lái)的方法。(第八章詳細(xì)介紹?)
方法區(qū)中根據(jù)類名搜索類信息,算法:散列、搜索樹(shù)等。
3.2 Java棧
虛擬機(jī)為每個(gè)線程生成一個(gè)Java棧,因而對(duì)不同的線程,棧內(nèi)的數(shù)據(jù)都是私有的。Java棧由棧幀組成,Java棧的操作只有兩種,壓入棧幀和彈出棧幀。線程中每個(gè)方法的調(diào)用都會(huì)在Java棧壓入一個(gè)棧幀;每次方法返回(正常方法或拋異常返回),該方法對(duì)應(yīng)的棧幀都會(huì)從棧中彈出。
3.2.1 棧幀
棧幀由操作數(shù)棧、局部變量區(qū)和棧幀數(shù)據(jù)組成。由于Java中的指令是基于棧而設(shè)計(jì)的,因而很多指令的默認(rèn)操作數(shù)就是操作數(shù)棧中的數(shù)據(jù)。操作數(shù)棧用于保存指令的操作數(shù)和指令操作后的結(jié)果。
局部變量區(qū)用于保存當(dāng)前方法的局部變量。
棧幀數(shù)據(jù)區(qū)則保存當(dāng)前棧幀的信息,如指向當(dāng)前類常量池的指針,用于操作數(shù)為常量池索引的指令;還有一些和特定虛擬機(jī)實(shí)現(xiàn)相關(guān)的信息和調(diào)試信息。
3.3 程序寄存器
每個(gè)線程在執(zhí)行時(shí)都會(huì)保存當(dāng)前指令的下一條指令的地址,以控制程序的之行流程。
3.4 堆
堆保存了程序在運(yùn)行時(shí)的所有對(duì)象。在Java中,所有的對(duì)象都是保存在堆中的,而外部通過(guò)對(duì)象的引用來(lái)訪問(wèn)對(duì)象。由于Java存在垃圾回收器,因而Java對(duì)象可能被移動(dòng),以減少內(nèi)存碎片。其中一種實(shí)現(xiàn)可以很好的解決移動(dòng)對(duì)象而需要改變所有該對(duì)象的引用變量的技術(shù),即將堆分為句柄池和對(duì)象池。對(duì)象池中的對(duì)象保存了對(duì)象的真正內(nèi)容,而句柄池中的項(xiàng)包含兩個(gè)指針,一個(gè)指向?qū)ο螅粋€(gè)指向類數(shù)據(jù)。一個(gè)對(duì)象引用就是指向句柄的之戰(zhàn)。這樣當(dāng)需要移動(dòng)對(duì)象時(shí),只要改變句柄池中指向?qū)ο蟮闹羔樦导纯伞H欢@種設(shè)計(jì)是以犧牲速度為代價(jià)的,因?yàn)檫@樣每次訪問(wèn)對(duì)象就要多經(jīng)歷一次指針定位。
在某些垃圾回收器實(shí)現(xiàn)中,對(duì)象需要額外的信息,如果引用計(jì)數(shù)的垃圾收集器,需要為每個(gè)對(duì)象記錄引用計(jì)數(shù)信息;而對(duì)另外有些機(jī)制,則可能需要暫時(shí)保存某些數(shù)據(jù)。這些額外的數(shù)據(jù)可以保存在類中,也可以在記錄在其他地方。類似的還有同步機(jī)制中的數(shù)據(jù)和記錄是否已經(jīng)調(diào)用過(guò)finalize方法的信息。
在Java中有指令用于在內(nèi)存中分配對(duì)象,卻沒(méi)有顯式的指令來(lái)釋放內(nèi)存中的對(duì)象。
3.5 本地方法棧
當(dāng)Java方法調(diào)用本地方法的時(shí)候,當(dāng)前線程的程序寄存器是不確定的值。程序的執(zhí)行也轉(zhuǎn)向本地方法。本地方法可以正常返回,也可以拋出異常。拋出的異常會(huì)在調(diào)用該本地方法的指令中重新拋出。
4. 執(zhí)行引擎
每個(gè)用戶線程(即不包括垃圾回收線程等)都有一個(gè)執(zhí)行引擎實(shí)例,用以執(zhí)行字節(jié)碼指令。
5. 本地方法接口
Java程序可以通過(guò)本地方法接口來(lái)調(diào)用本地方法。
于2010-10-06