Java虛擬機(jī)體系結(jié)構(gòu)

方法區(qū)
在Java虛擬機(jī)中,被裝載類型的信息存儲(chǔ)在一個(gè)邏輯上被稱為方法區(qū)的內(nèi)存中。
當(dāng)虛擬機(jī)裝載某個(gè)類型時(shí),它使用類裝載器定位相應(yīng)的class文件,-->讀入這個(gè)class文件(一個(gè)線性的二進(jìn)制流)->將它傳入虛擬機(jī)-->
虛擬機(jī)提取類型信息,并將信息存入方法區(qū),類型中的類(靜態(tài))變量也存儲(chǔ)在方法區(qū).
方法區(qū)特點(diǎn):
1)所有線程共享方法區(qū)。它是線程安全的。
2)方法區(qū)大小不是固定的。虛擬機(jī)根據(jù)需要自行調(diào)整。
3)方法區(qū)可以被垃圾回收。
對(duì)于每個(gè)被裝載的類型,虛擬機(jī)會(huì)在方法區(qū)中存儲(chǔ)以下信息。
1)類型的基本信息;
a)類型的全限定名
b)類型的直接超類全限定名(除非這個(gè)類型是java.lang.Objet,它沒(méi)超類)。
c)類型是類類型還是接口類型(就是說(shuō)是一個(gè)類還是一個(gè)接口)。
d)類型的訪問(wèn)修飾符(public ,abstract或final的某個(gè)子類)
e)任何直接超接口的全限定名的有序列表。
2)該類型的常量池
虛擬機(jī)必須為每個(gè)被裝載的類型維護(hù)一個(gè)常量池。常量池就是該類型所用常量的一個(gè)有序集合,
包括直接常量(string,integer,floating point常量)和對(duì)其他類型、字段和方法的符號(hào)引用。
池中的數(shù)據(jù)項(xiàng)就像數(shù)組一樣是通過(guò)索引訪問(wèn)的。因?yàn)槌A砍卮鎯?chǔ)了相應(yīng)類型所用到的所有類型、
字段和方法的符號(hào)引用,所以它在Java程序的動(dòng)態(tài)連接中起著核心的作用。
3)字段信息
類型中聲明的每一個(gè)字段,方法區(qū)中必須保存下面的信息,字段在類或接口中聲明的順序也必須保存。
字段名,字段類型,字段修飾符(public private protected static final 等)
4)方法信息
類型中聲明的每一個(gè)方法,方法區(qū)中必須保存下面的信息,方法在類或接口中聲明的順序也必須保存。
方法名,返回值類型,參數(shù)數(shù)量和類型(按聲明順序),方法修飾符(public private protected static final 等)
如果方法不是抽象的或本地的還必須保存:方法字節(jié)碼,操作數(shù)棧和該方法在棧針中局部變量的大小,異常表。
5)除了常量以外的所有類(靜態(tài))變量
這里主要說(shuō)下編譯時(shí)常量:就是那些用final聲明以及編譯時(shí)已知的值初始化的類變量(例如:static final int val =5)
每個(gè)編譯時(shí)常量的類型都會(huì)復(fù)制它所有常量到它自己的常量池中或者它的字節(jié)碼流中(通常情況下編譯時(shí)直接替換字節(jié)碼)。
6)一個(gè)到類classLoader的引用
指向ClassLoader類的引用 每個(gè)類型被裝載的時(shí)候,虛擬機(jī)必須跟蹤它是由啟動(dòng)類裝載器
還是由用戶自定義類裝載器裝載的。如果是用戶自定義類裝載器裝載的,那么虛擬機(jī)必須在類
型信息中存儲(chǔ)對(duì)該裝載器的引用:這是作為方法表中的類型數(shù)據(jù)的一部分保存的。
虛擬機(jī)會(huì)在動(dòng)態(tài)連按期間使用這個(gè)信息。當(dāng)某個(gè)類型引用另一個(gè)類型的時(shí)候,虛擬機(jī)會(huì)請(qǐng)求裝載
發(fā)起引用類型的類裝載器來(lái)裝載被引用的類型。這個(gè)動(dòng)態(tài)連接的過(guò)程,對(duì)于虛擬機(jī)分離命名空間
的方式也是至關(guān)重要的。為了能夠正確地執(zhí)行動(dòng)態(tài)連接以及維護(hù)多個(gè)命名空間,虛擬機(jī)需要在方
法表中得知每個(gè)類都是由哪個(gè)類裝載器裝載的。
7)一個(gè)到Class類的引用
指向Class類的引用 對(duì)于每一個(gè)被裝載的類型(不管是類還是接口),虛擬機(jī)都會(huì)相應(yīng)地為
它創(chuàng)建一個(gè)java.lang.Class類的實(shí)例(Class實(shí)例放在內(nèi)存中的堆區(qū)),而且虛擬機(jī)還必須以某種方式把這個(gè)實(shí)例的引用存儲(chǔ)在方法區(qū)
為了盡可能提高訪問(wèn)效率,設(shè)計(jì)者必須仔細(xì)設(shè)計(jì)存儲(chǔ)在方法區(qū)中的類型信息的數(shù)據(jù)結(jié)構(gòu),因此,
除了以上時(shí)論的原始類型信息,實(shí)現(xiàn)中還可能包括其他數(shù)據(jù)結(jié)構(gòu)以加快訪問(wèn)原始數(shù)據(jù)的速度,比如方法表。
虛擬機(jī)對(duì)每個(gè)裝載的非抽象類,都生成一個(gè)方法表,把它作為類信息的一部分保存在方法區(qū)。方法表是一個(gè)數(shù)組,
它的元素是所有它的實(shí)例可能被調(diào)用的實(shí)例方法的直接引用,包括那些從超類繼承過(guò)來(lái)的實(shí)例方法:(對(duì)于抽象類和接口,方法表沒(méi)有什么幫
助,因?yàn)槌绦驔Q不會(huì)生成它們的實(shí)例。)運(yùn)行時(shí)可以通過(guò)方法表快速搜尋在對(duì)象中調(diào)用的實(shí)例方法。
方法區(qū)使用的例子
class Lava{
private int speed = 5;
void flow(){
}
}
public class Volcano {
public static void main(String args[]){
Lava lava = new Lava();
lava.flow();
}
}
1)虛擬機(jī)在方法區(qū)查找Volcano這個(gè)名字,未果,載入volcano.class文件,并提取相應(yīng)信息
存入方法區(qū)。
2)虛擬機(jī)開(kāi)始執(zhí)行Volcano類中main()方法的字節(jié)碼的時(shí)候,盡管Lava類還沒(méi)被裝載,
但是和大多數(shù)(也許所有)虛擬機(jī)實(shí)現(xiàn)一樣,它不會(huì)等到把程序中用到的所有類都裝載后才開(kāi)
始運(yùn)行程序。恰好相反,它只在需要時(shí)才裝載相應(yīng)的類。
3)main()的第一條指令告知虛擬機(jī)為列在常量池第一項(xiàng)的類分配足夠的內(nèi)存。所以虛擬機(jī)
使用指向Volcano常量池的指針找到第一項(xiàng),發(fā)現(xiàn)它是一個(gè)對(duì)Lava類的符號(hào)引用,然后它就檢查
方法區(qū),看Lava類是否已經(jīng)被裝載了。
4)當(dāng)虛擬機(jī)發(fā)現(xiàn)還沒(méi)有裝載過(guò)名為"Lava"的類時(shí),它就開(kāi)始查找并裝載文件“Lava.class”,
并把從讀入的二進(jìn)制數(shù)據(jù)中提取的類型信息放在方法區(qū)中。
5)虛擬機(jī)以一個(gè)直接指向方法區(qū)Lava類數(shù)據(jù)的指針來(lái)替換常量池第—項(xiàng)(就是那個(gè)
字符串“Lava”)——以后就可以用這個(gè)指針來(lái)快速地訪問(wèn)Lava類了。這個(gè)替換過(guò)程稱為常量池
解析,即把常量池中的符號(hào)引用替換為直接引用:這是通過(guò)在方法區(qū)中搜索被引用的元素實(shí)現(xiàn)
的,在這期間可能又需要裝載其他類。在這里,我們替換掉符號(hào)引用的“直接引用”是一個(gè)本
地指針。
6)虛擬機(jī)準(zhǔn)備為一個(gè)新的Lava對(duì)象分配內(nèi)存。此時(shí),它又需要方法區(qū)中的信息。還記
得剛剛放到Volcano類常量池第——項(xiàng)的指針嗎?現(xiàn)在虛擬機(jī)用它來(lái)訪問(wèn)Lava類型信息(此前剛放
到方法區(qū)中的),找出其中記錄的這樣一個(gè)信息:一個(gè)Lava對(duì)象需要分配多少堆空間。
7)虛擬機(jī)確定一個(gè)Lava對(duì)象大小后,就在堆上分配空間,并把這個(gè)對(duì)象實(shí)例變量speed初始化為默認(rèn)初始值0
8)當(dāng)把新生成的Lava對(duì)象的引用壓到棧中,main()方法的第一條指令也完成了,指令通過(guò)這個(gè)引用
調(diào)用Java代碼(該代碼把speed變量初始化為正確初始值5).另外用這個(gè)引用調(diào)用Lava對(duì)象引用的flow()方法。
堆
每個(gè)java虛擬機(jī)實(shí)例都有一個(gè)方法區(qū)以及一個(gè)堆,一個(gè)java程序獨(dú)占一個(gè)java虛擬機(jī)實(shí)例,而每個(gè)java程序都有自己的堆空間,它們不會(huì)彼此干擾,但同一個(gè)java程序的多個(gè)線程共享一個(gè)堆空間。這種情況下要考慮多線程訪問(wèn)同步問(wèn)題。
Java棧
一個(gè)新線程被創(chuàng)建時(shí),都會(huì)得到自己的PC寄存器和一個(gè)java棧,虛擬機(jī)為每個(gè)線程開(kāi)辟內(nèi)存區(qū)。這些內(nèi)存區(qū)是私有的,任何線程不能訪問(wèn)其他線程的PC寄存器和java棧。java??偸谴鎯?chǔ)該線程中java方法的調(diào)用狀態(tài)。包括它的局部變量,被調(diào)用時(shí)傳進(jìn)來(lái)的參數(shù),它的返回值,以及運(yùn)算的中間結(jié)果等。java棧是由許多棧幀或者說(shuō)幀組成,一個(gè)棧幀包含一個(gè)java方法的調(diào)用狀態(tài),當(dāng)線程調(diào)用java方法時(shí),虛擬機(jī)壓入一個(gè)新的棧幀到該線程的java棧中。當(dāng)方法返回時(shí),這個(gè)棧幀被從java棧中彈出并拋棄。
.本地方法棧
任何本地方法接口都會(huì)使用某種本地方法餞。當(dāng)線程調(diào)用Java方法時(shí),虛擬機(jī)會(huì)創(chuàng)建一個(gè)新的棧幀井壓人Java棧。
然而當(dāng)它調(diào)用的是本地方法時(shí),虛擬機(jī)會(huì)保持Java棧不變,不再在線程的Java棧中壓人新的幀,虛擬機(jī)只是簡(jiǎn)單地動(dòng)態(tài)連接
并直接調(diào)用指定的本地方法??梢园堰@看做是虛擬機(jī)利用本地方法來(lái)動(dòng)態(tài)擴(kuò)展自己。