Java虛擬機(jī)及JVM體系結(jié)構(gòu)
JVM(Java 虛擬機(jī))
Java虛擬機(jī),java源文件(.java)通過編譯器生成字節(jié)碼文件(.class),字節(jié)碼文件(.class)通過JVM(Java虛擬機(jī))中的解釋器再翻譯成特定機(jī)器上的機(jī)器碼。
編譯程序只需要面向虛擬機(jī),生成虛擬機(jī)能夠理解的代碼,然后由解釋器來將虛擬機(jī)代碼轉(zhuǎn)換為特定系統(tǒng)的機(jī)器碼執(zhí)行。
每一種平臺(tái)的解釋器是不同的,但是實(shí)現(xiàn)的虛擬機(jī)是相同的。
Java源程序經(jīng)過編譯器編譯后變成字節(jié)碼,字節(jié)碼由虛擬機(jī)解釋執(zhí)行,虛擬機(jī)將每一條要執(zhí)行的字節(jié)碼送給解釋器,解釋器將其翻譯成特定機(jī)器上的機(jī)器碼,然后在特定的機(jī)器上運(yùn)行。
JVM體系結(jié)構(gòu)
JVM都有兩種機(jī)制,一個(gè)是裝載具有合適名稱的類(類或是接口),叫做類裝載子系統(tǒng);另外的一個(gè)負(fù)責(zé)執(zhí)行包含在已裝載的類或接口中的指令,叫做運(yùn)行引擎。每個(gè)JVM又包括方法區(qū)、堆、Java棧、程序計(jì)數(shù)器和本地方法棧這五個(gè)部分,這幾個(gè)部分和類裝載機(jī)制與運(yùn)行引擎機(jī)制一起組成的體系結(jié)構(gòu)圖為:
JVM的每個(gè)實(shí)例都有一個(gè)它自己的方法域和一個(gè)堆,運(yùn)行于JVM內(nèi)的所有的線程都共享這些區(qū)域;當(dāng)虛擬機(jī)裝載類文件的時(shí)候,它解析其中的二進(jìn)制數(shù)據(jù)所包含的類信息,并把它們放到方法域中;當(dāng)程序運(yùn)行的時(shí)候,JVM把程序初始化的所有對(duì)象置于堆上;而每個(gè)線程創(chuàng)建的時(shí)候,都會(huì)擁有自己的程序計(jì)數(shù)器和Java棧,其中程序計(jì)數(shù)器中的值指向下一條即將被執(zhí)行的指令,線程的Java棧則存儲(chǔ)為該線程調(diào)用Java方法的狀態(tài);本地方法調(diào)用的狀態(tài)被存儲(chǔ)在本地方法棧,該方法棧依賴于具體的實(shí)現(xiàn)。
(1)類裝載子系統(tǒng)
裝載 連接 初始化
(2)方法區(qū)。被所有線程共享。垃圾收集也會(huì)清理方法區(qū)中的無用類型對(duì)象。
a. 類型信息。類加載器加載類時(shí),從類文件中提取出來。
類的完整有效名
父類的完整有效名(interface and java.lang.Object 除外,因?yàn)闊o父類)
類型的修飾符
類型直接接口列表
b. 常量池。存儲(chǔ)了一個(gè)類型所使用的常量所有類型、域和方法的符號(hào)引用。
c. 域信息。jvm必須在方法區(qū)中保存類型的所有域的相關(guān)信息以及域的聲明順序, 域的相關(guān)信息包括: 域名 域類型 域修飾符(public private protected static final volatile transient…)
d.方法信息。
方法名
方法返回類型
方法參數(shù)
方法的修飾符
方法的字節(jié)碼(abstract and native 除外)(被PC寄存器指向)
操作數(shù)棧和方法棧幀的局部變量區(qū)的大小
異常表
e. 類的靜態(tài)變量(所有對(duì)象共享一分拷貝)
f. 類的被聲明為final的類變量(所有對(duì)象共享一分拷貝)
g. 加載一個(gè)類的類加載器的引用
h. Class類的引用
i. 方法表。
j. 一個(gè)例子:
- Class Lava {
- private int speed = 5;
- void flow();
- }
- Class Volcano {
- public static void main(String[] args) {
- Lava lava = new Lava();
- lava.flow();
- }
- }
下面我們描述一下main()方法的***條指令的字節(jié)碼是如何被執(zhí)行 的。不同的jvm實(shí)現(xiàn)的差別很大,這里只是其中之一。
為了運(yùn)行這個(gè)程序,你以某種方式把“Volcano"傳給了jvm。有了 這個(gè)名字,jvm找到了這個(gè)類文件(Volcano.class)并讀入,它從 類文件提取了類型信息并放在了方法區(qū)中,通過解析存在方法區(qū)中的 字節(jié)碼,jvm激活了main()方法,在執(zhí)行時(shí),jvm保持了一個(gè)指向當(dāng)前 類(Volcano)常量池的指針。
注意jvm在還沒有加載Lava類的時(shí)候就已經(jīng)開始執(zhí)行了。正像大多數(shù)的 jvm一樣,不會(huì)等所有類都加載了以后才開始執(zhí)行,它只會(huì)在需要的時(shí)候 才加載。
main()的***條指令告知jvm為列在常量池***項(xiàng)的類分配足夠的內(nèi)存。 jvm使用指向Volcano常量池的指針找到***項(xiàng),發(fā)現(xiàn)是一個(gè)對(duì)Lava類 的符號(hào)引用,然后它就檢查方法區(qū)看lava是否已經(jīng)被加載了。
這個(gè)符號(hào)引用僅僅是類lava的完整有效名”lava“。這里我們看到為了jvm 能盡快從一個(gè)名稱找到一個(gè)類,一個(gè)良好的數(shù)據(jù)結(jié)構(gòu)是多么重要。這里jvm 的實(shí)現(xiàn)者可以采用各種方法,如hash表,查找樹等等。同樣的算法可以用于 Class類的forName()的實(shí)現(xiàn)。
當(dāng)jvm發(fā)現(xiàn)還沒有加載過一個(gè)稱為"Lava"的類,它就開始查找并加載類 文件"Lava.class"。它從類文件中抽取類型信息并放在了方法區(qū)中。
jvm于是以一個(gè)直接指向方法區(qū)lava類的指針替換了常量池***項(xiàng)的符號(hào) 引用。以后就可以用這個(gè)指針快速的找到lava類了。而這個(gè)替換過程稱為 常量池解析(constant pool resolution)。在這里我們替換的是一個(gè) native指針。
jvm終于開始為新的lava對(duì)象分配空間了。這次,jvm仍然需要方法區(qū)中 的信息。它使用指向lava數(shù)據(jù)的指針(剛才指向volcano常量池***項(xiàng)的指針) 找到一個(gè)lava對(duì)象究竟需要多少空間。
一旦jvm知道了一個(gè)Lava對(duì)象所要的空間,它就在堆上分配這個(gè)空間并把這個(gè)實(shí)例的變量speed初始化為缺省值0。假如lava的父對(duì)象也有實(shí)例變量,則也會(huì)初始化。
當(dāng)把新生成的lava對(duì)象的引用壓到棧中,***條指令也結(jié)束了。下面的指令利用這個(gè)引用激活java代碼把speed變量設(shè)為初始值,5。另外一條指令會(huì)用這個(gè)引用激活 Lava對(duì)象的flow()方法。
(3)堆。存放運(yùn)行時(shí)所有 對(duì)象 和 數(shù)組。
(4)棧。每次啟動(dòng)一個(gè)新的線程,就會(huì)被分配一個(gè)棧。
(5)PC 寄存器(程序計(jì)數(shù)器)總是指向該線程下一步要執(zhí)行的指令。指令的位置放在方法區(qū)的方法字節(jié)碼中。內(nèi)容是相 對(duì)于***個(gè)指令的偏移量。
(6)本地方法棧。
讓我們?yōu)榱酥袊?guó)軟件產(chǎn)業(yè)的振興,一起努力!
原文鏈接:http://www.cnblogs.com/huaihai/archive/2011/11/09/2242010.html
【編輯推薦】