java虛擬機器的三種含義:
- 抽象的規範
- 一個具體的實現
- 一個運行中的虛擬機器執行個體
---------------------java虛擬機器的生命週期:
java虛擬機器執行個體的天職就是負責運行一個java程式。
啟動一個java程式,一個虛擬機器執行個體誕生了;程式關閉退出,虛擬機器消亡。
有幾個java程式正在運行,就有幾個java虛擬機器執行個體。每個java程式都運行在自己的java虛擬機器執行個體中。
java虛擬機器中有兩種線程:
- 守護線程:由虛擬機器自己使用的(執行垃圾收集線程),java程式也可以把任何線程標記為守護線程。
- 非守護線程:main()這種。
非守護線程都終止時,虛擬機器執行個體才自動結束。(也可以調用Runtime或System的exit()方法退出程式)。
---------------------java虛擬機器的體繫結構:
見同目錄第五章.Java虛擬機器-體繫結構圖!
方法區和堆是該虛擬機器執行個體的所有線程共用的。
當虛擬機器裝載一個class檔案時,他會從這個class檔案包含的位元據中解析類型資訊,然後把這些類型資訊放到方法區中。
當程式運行時,虛擬機器會把所有該程式在運行時建立的對象都放到堆中。
每個新線程被建立時,他都將得到它自己的PC寄存器以及一個java棧(不是所有線程共用)。
java虛擬機器為每個線程建立記憶體區,這些記憶體地區是私人的,任何線程都不能訪問另一個線程的PC寄存器或者java棧。
如果線程正在執行的是一個java方法(非本地方法),那麼PC寄存器的值將總是指示下一條將被執行的指令。
java棧則總是儲存該線程中java方法調用的狀態(局部變數、被調用時傳進來的參數、傳回值、運算的中間結果)
本地方法調用的狀態則是以某種依賴於具體實現的方式儲存在本地方法棧中,也可能在寄存器或者其他某些特定實現相關的記憶體區中。
---------------------棧幀(1):
java棧上由許多棧幀(幀)組成的,一個棧幀包含一個java方法調用的狀態。
當線程調用一個java方法時,虛擬機器壓入一個新的棧幀到該線程的java棧中;當方法返回時,這個棧幀被從java棧中彈出並拋棄。
java虛擬機器沒有寄存器,其指令集使用java棧來儲存中間資料(為了平台無關性)。
---------------------類型資料
類型資料分為來那個中:
基本類型:float、double、byte、short、int、long、char、boolean
參考型別:reference(類引用、介面引用、數組引用)
注意:編譯成位元組碼時boolean用int表示,
java虛擬機器中,最基本的資料單元就是字,至少選擇32位作為字長。
運行時資料區的大部分內容,都是基於“字”這個抽象概念的。
---------------------類裝載器子系統:
類裝載器的裝載、串連和初始化:
- 裝載:尋找並裝載類型的位元據
- 串連:執行驗證,準備,以及解析(可選)。
驗證:確保被匯入類型的正確性。
準備:為靜態變數分配記憶體,並將其初始化為預設值。
解析:把類型中的符號引用轉換為直接引用。
- 初始化:把靜態變數初始化為正確初始值。
啟動類裝載器:
只要是符合java class檔案格式的二進位檔案,java虛擬機器實現都必須能夠從中辨別並裝載器中的類和介面。每個java虛擬機器實現必須有一個啟動類載入器來載入受信任的類,比如java API。
使用者自訂類裝載器:
使用者自訂的類裝載器是普通的java對象,它的類必須派生自java.lang.ClassLoader類。ClassLoader類中定義的方法為程式提供了訪問類裝載器機制的介面。
使用者自訂類裝載器和Class類的執行個體都放在記憶體中的堆區,而裝載的類型資訊則都位於方法區。
ClassLoader類的四個通往java虛擬機器的方法:
protected final Class defineClass(String name, byte data[], int offset, int length);
接收一個名為data[]的位元組數組,並且在data[offset]到data[data+length]之間的位元據必須符合java class格式(表示一個新的可用類),name是該類型的全限定名,並將賦以預設的保護域。注意:該方法返回Class對象執行個體時表示指定的class檔案已經被找到並裝載到了方法區(不一定串連和初始化)
protected final Class defineClass(String name, byte data[], int offset, int length,
ProtectionDomain protectionDomain);
與前面一樣,只是,該類型的保護域將由它的protectionDomain參數指定。
protected final Class findSystemClass(String name);
接收一個字串name(該類型的全限定名),用於啟動系統類別裝載器(必須保證能啟動)。
protected final void resolveClass(Class c);
接收一個Class執行個體的引用作為參數,它將對該Class執行個體表示的類型執行串連動作(必須保證能執行串連動作)。
---------------------方法區:
所有線程都共用方法區,所以它們對方法區資料的訪問必須被設計為是安全執行緒的。
方法區的大小不必說固定的,虛擬機器可以根據應用的需要動態調整。
方法區不必是連續的。
方法區也可以被垃圾收集。
類型資訊:
對每個裝載的類型,虛擬機器都會在方法區中儲存一下類型資訊:
- 該類型全限定名
- 該類型的直接超類的全限定名(除非是Object類,沒有超類)
- 該類型是類類型還是介面類型
- 該類型的存取修飾詞(public、abstract、final的某個子集)
- 任何型直接超介面的全限定名的有序列表
- 該類型的常量池【核心】:所有常量的類型欄位方法的符號引用(索引訪問)
- 欄位資訊:欄位名,欄位類型,欄位修飾符
- 方法資訊:方法名,傳回型別,參數數量和類型(按序),方法修飾符
- 除了常量以外的靜態變數
- 一個到類ClassLoader的引用:跟蹤儲存該類載入時所用的載入器的類別
- 一個到Class類的引用:虛擬機器會建立一個相應的java.lang.Class類的執行個體
---------------------方法表:
方法表:類型資料結構
虛擬機器為每個非抽象類別都產生一個表,把它作為類資訊的一部分儲存在方法區。
方法表是一個數組,元素是所有它的執行個體可能被調用的執行個體方法的直接引用
,包括是超類直接繼承過來的方法。運行時可以通過方法錶快速搜尋在對象中調用的執行個體方法。
---------------------堆:
java程式在運行時建立的所有類執行個體或數組都放在同一個堆中(要考慮線程同步問題)。
和方法區一樣,堆空間也不是連續的記憶體區。
---------------------數組:
數組的內部表示:
在java中,數組才是真正的對象
和其他對象一樣,數組總是儲存在堆中。
和其他對象一樣,數組也有一個與他們的類關聯的Class執行個體,所有具有相同維度和類型的數組都是同一個類的執行個體(不管數組的長度是多少)。
數組類的名稱由兩部分組成,每一維度用一個"["表示,用字元或字串表示元素類型。如“[[Ljava/lang/Object”表示元素為Object的二維數組。
在堆中,每一個數組對象還必須要儲存數組的長度、數組資料以及某些指向數組的類資料的引用。
---------------------程式計數器(PC寄存器):
每一個線程都有它自己的PC寄存器,它是在該線程啟動的時候建立的。
PC寄存器的大小是字長,因此能夠持有一個本地指標,也能夠持有一個returnAddress。當線程執行某個java方法時,PC寄存器的內容總是寫一條將被執行的指令的地址,這裡的地址可以是個本地指標,也可以是在方法位元組碼中相對於該方法其實指令的位移量。如果該線程正在執行一個本地方法,此時PC寄存器的值是“undefined”。
---------------------Java棧:
每當啟動一個新線程時,java虛擬機器都會為它分配一個java棧。
虛擬機器智慧直接對java棧執行兩種操作:以幀為單位的壓棧或出棧。
每當線程調用一個java方法時,虛擬機器都會在該java棧中壓入一個新幀。
java方法不管是正常返回還是拋異常中止,虛擬機器都會將當前棧彈出java棧,然後釋放記憶體。
java棧和棧幀在記憶體中也不必說連續的。
---------------------棧幀(2):
棧幀的構成:
- 局部變數區:
是一個以字長為單位,從0開始計數的數組。位元組碼指令通過從0開始的索引來使用其中的資料。
int、float、reference和returnAddress的值在數組中只佔一項;
byte、short、char的值在存入數組前都被轉換成int,所以也只佔一項;
boolean虛擬機器不支援,用int來表示,所以也只佔一項;
long、double的值在數組中佔兩項(使用只要指出第一項索引值)。
局部變數區包含對應的方法參數和局部變數。
按參數的順序來存入局部變數數組中。真正的局部變數順序任意。
- 運算元棧:
是一個以位元組為單位的數組(不是通過索引來訪問),是通過標準的棧操作(壓棧和出棧)來訪問。
儲存的資料類型和局部變數區的一樣。
虛擬機器把運算元棧作為他的工作區,大多數指令都要從這裡彈出資料,執行計算,然後把結果壓回到運算元棧中。
- 幀資料區:
常量池解析、正常方法返回、異常派發機制。
每當虛擬機器要執行某個需要用到常量池資料的指令時,它都會通過幀資料區中指向常量池的指標來訪問它。
幀資料區還要協助虛擬機器處理java方法的正常結束或異常中止:
如果正常結束:虛擬機器必須恢複發起調用的方法的(上一個)棧幀,調用完成方法的指令的下一個指令。如果有傳回值,虛擬機器將把傳回值壓倒發
起調用的方法的運算元棧。
如果拋出異常,根據幀資料區的異常表來處理。如果有catch語句,就會交給catch中的代碼,如果沒有,則立即異常中止。
---------------------本地方法棧:
當線程調用本地方法時,虛擬機器會保持java棧不變,不再線上程的java棧中壓入新的幀,虛擬機器只是簡單地動態串連並直接調用指定的本地方法。
就像其他運行時記憶體區一樣,本地方法棧佔用的記憶體區也不必說固定大小的,它可以根據需要動態擴充或者收縮。
---------------------執行引擎:
任何java虛擬機器實現的核心都是它的執行引擎。
在java虛擬機器規範中,執行引擎的行為使用指令集來定義。
作為運行時執行個體的執行引擎就是一個線程。
運行中java程式的每一個線程都是一個獨立的虛擬機器執行引擎的執行個體。從生命週期的開始到結束,它要麼在執行位元組碼,要麼在執行本地方法。