Java虛擬機體系結構

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