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程序的每一個線程都是一個獨立的虛擬機執行引擎的實例。從生命周期的開始到結束,它要么在執行字節碼,要么在執行本地方法。