第2章Java虛擬機(jī)的結(jié)構(gòu)
本文檔指定了一個抽象機(jī)器。它沒有描述Java虛擬機(jī)的任何特定實現(xiàn)。
要正確實現(xiàn)Java虛擬機(jī),您只需要能夠讀取class文件格式并正確執(zhí)行其中指定的操作。不屬于Java虛擬機(jī)規(guī)范的實現(xiàn)細(xì)節(jié)會不必要地限制實現(xiàn)者的創(chuàng)造力。例如,運(yùn)行時數(shù)據(jù)區(qū)域的內(nèi)存布局,使用的垃圾收集算法以及Java虛擬機(jī)指令的任何內(nèi)部優(yōu)化(例如,將它們轉(zhuǎn)換為機(jī)器代碼)由實現(xiàn)者自行決定。
2.1。該class文件格式
由Java虛擬機(jī)執(zhí)行的編譯代碼使用獨(dú)立于硬件和操作系統(tǒng)的二進(jìn)制格式表示,通常(但不一定)存儲在文件中,稱為class文件格式。該class文件格式精確定義的類或接口,其中包括詳細(xì)信息,如字節(jié)順序理所當(dāng)然在特定平臺的目標(biāo)文件格式可能采取的代表性。
第4章“ class文件格式” class詳細(xì)介紹了文件格式。
2.2。數(shù)據(jù)類型
與Java編程語言一樣,Java虛擬機(jī)也可以使用兩種類型:基本類型 和引用類型。相應(yīng)地,有兩種值可以存儲在變量中,作為參數(shù)傳遞,由方法返回,并對其進(jìn)行操作:原始值和參考值。
java虛擬機(jī)期望幾乎所有類型檢查都在運(yùn)行時之前完成,通常由編譯器完成,而不必由Java虛擬機(jī)本身完成。原始類型的值不需要被標(biāo)記或以其他方式可檢查以在運(yùn)行時確定它們的類型,或者與引用類型的值區(qū)分。相反,Java虛擬機(jī)的指令集使用旨在對特定類型的值進(jìn)行操作的指令來區(qū)分其操作數(shù)類型。例如,*iadd*,*ladd*,*fadd*和 *dadd*都是Java虛擬機(jī)指令添加兩個數(shù)值,并產(chǎn)生數(shù)值結(jié)果,但每個專業(yè)的操作數(shù)類型: `int`,`long`,`float`,和`double`分別。有關(guān)Java虛擬機(jī)指令集中類型支持的摘要,請參見 [§2.11.1](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.11.1 "2.11.1。 類型和Java虛擬機(jī)")。
Java虛擬機(jī)包含對對象的顯式支持。對象是動態(tài)分配的類實例或數(shù)組。對對象的引用被認(rèn)為具有Java虛擬機(jī)類型reference。類型的值reference可以被認(rèn)為是指向?qū)ο蟮闹羔?。可能存在多個對象的引用。始終通過類型值操作,傳遞和測試對象 reference。
2.3。原始類型和價值觀
Java虛擬機(jī)支持的原始數(shù)據(jù)類型是數(shù)字類型, boolean類型(§2.3.4)和returnAddress 類型(§2.3.3)。
數(shù)字類型由整數(shù)類型(§2.3.1)和浮點(diǎn)類型 (§2.3.2)組成。
整體類型是:
* `byte`,其值為8位有符號二進(jìn)制補(bǔ)碼整數(shù),其默認(rèn)值為零
* `short`,其值為16位有符號二進(jìn)制補(bǔ)碼整數(shù),其默認(rèn)值為零
* `int`,其值為32位有符號二進(jìn)制補(bǔ)碼整數(shù),其默認(rèn)值為零
* `long`,其值為64位有符號二進(jìn)制補(bǔ)碼整數(shù),其默認(rèn)值為零
* `char`,其值為16位無符號整數(shù),表示基本多語言平面中的Unicode代碼點(diǎn),使用UTF-16編碼,其默認(rèn)值為空代碼點(diǎn)(`'\u0000'`)
浮點(diǎn)類型是:
* `float`,其值是浮點(diǎn)值集的元素,或者,如果支持,則為float-extended-exponent值集,其默認(rèn)值為正零
* `double`,其值是double值集的元素,或者,如果支持,則為double-extended-exponent值集,其默認(rèn)值為正零
所述的值`boolean` 類型編碼的真值`true`和`false`,并且缺省值是`false`。
第一版*的的Java ?虛擬機(jī)規(guī)范*并不認(rèn)為 `boolean`是一個Java虛擬機(jī)類型。但是,`boolean`值在Java虛擬機(jī)中的支持有限。第二版*的的Java ?虛擬機(jī)規(guī)范* 的治療澄清這個問題`boolean`作為一個類型。
該`returnAddress`類型的值 是指向Java虛擬機(jī)指令的操作碼的指針。在原始類型中,只有`returnAddress`類型與Java編程語言類型沒有直接關(guān)聯(lián)。
程序計數(shù)寄存器:
每個線程都有自己的程序計數(shù)寄存器、
每個Java虛擬機(jī)線程都在執(zhí)行單個方法的代碼、
如果線程執(zhí)行的不是 native 方法,則程序計數(shù)寄存器包含當(dāng)前正在執(zhí)行的Java虛擬機(jī)指令的地址。
如果線程執(zhí)行的是 native 方法,則程序計數(shù)寄存器的值未定義。
PC寄存器的內(nèi)容總是指向下一條將被執(zhí)行指令的地址,這里的地址可以是一個本地指針,也可以是在方法區(qū)中相對應(yīng)于該方法起始指令的偏移量。
Java虛擬機(jī)棧
每個Java虛擬機(jī)線程都有一個私有Java虛擬機(jī)棧,與線程同時創(chuàng)建。
Java虛擬機(jī)棧存儲幀。
Java虛擬機(jī)棧類似于傳統(tǒng)語言的棧,例如C:它保存局部變量和部分結(jié)果,并在方法調(diào)用和返回中起作用。
由于除了push和pop幀之外,永遠(yuǎn)不會直接操作Java虛擬機(jī)棧,因此可以對堆進(jìn)行堆分配。
Java虛擬機(jī)棧的內(nèi)存不需要是連續(xù)的。
Heap 堆
堆在Java虛擬機(jī)線程之間共享。
堆是運(yùn)行時數(shù)據(jù)區(qū),從中分配所有類實例和數(shù)組的內(nèi)存。
堆是在虛擬機(jī)啟動時創(chuàng)建的。對象的堆存儲由自動存儲管理系統(tǒng)(稱為垃圾收集器)回收 ; 對象永遠(yuǎn)不會被顯式釋放。Java虛擬機(jī)假設(shè)沒有特定類型的自動存儲管理系統(tǒng),可以根據(jù)實現(xiàn)者的系統(tǒng)要求選擇存儲管理技術(shù)。堆可以具有固定大小,或者可以根據(jù)計算的需要進(jìn)行擴(kuò)展,并且如果不需要更大的堆,則可以收縮。堆的內(nèi)存不需要是連續(xù)的。
Java虛擬機(jī)實現(xiàn)可以為程序員或用戶提供對堆的初始大小的控制,以及如果可以動態(tài)擴(kuò)展或收縮堆,則控制最大和最小堆大小。
類的對象放在heap(堆)中,所有的類對象都是通過new方法創(chuàng)建,創(chuàng)建后,在stack(棧)會創(chuàng)建類對象的引用(內(nèi)存地址)。
一種常規(guī)用途的內(nèi)存池(也在RAM(隨機(jī)存取存儲器 )區(qū)域),其中保存了Java對象。和棧不同:“內(nèi)存堆”或“堆”最吸引人的地方在于編譯器不必知道要從堆里分配多少存儲空間,也不必知道存儲的數(shù)據(jù)要在堆里停留多長的時間。因此,用堆保存數(shù)據(jù)時會得到更大的靈活性。要求創(chuàng)建一個對象時,只需用new命令編輯相應(yīng)的代碼即可。執(zhí)行這些代碼時,會在堆里自動進(jìn)行數(shù)據(jù)的保存。當(dāng)然,為達(dá)到這種靈活性,必然會付出一定的代價:在堆里分配存儲空間時會花掉更長的時間。
JVM將所有對象的實例(即用new創(chuàng)建的對象)(對應(yīng)于對象的引用(引用就是內(nèi)存地址))的內(nèi)存都分配在堆上,堆所占內(nèi)存的大小由-Xmx指令和-Xms指令來調(diào)節(jié)
方法區(qū)
method(方法區(qū))又叫靜態(tài)區(qū),存放所有的①類(class),②靜態(tài)變量(static變量),③靜態(tài)方法,④常量和⑤成員方法。
1.又叫靜態(tài)區(qū),跟堆一樣,被所有的線程共享。
2.方法區(qū)中存放的都是在整個程序中永遠(yuǎn)唯一的元素。這也是方法區(qū)被所有的線程共享的原因。
Java虛擬機(jī)具有在所有Java虛擬機(jī)線程之間共享的*方法區(qū)域*。
方法區(qū)域類似于傳統(tǒng)語言的編譯代碼的存儲區(qū)域或類似于操作系統(tǒng)進(jìn)程中的“文本”段。
它存儲每類結(jié)構(gòu),例如運(yùn)行時常量池,字段和方法數(shù)據(jù),以及方法和構(gòu)造函數(shù)的代碼,包括類和實例初始化以及接口初始化中使用的特殊方法.
方法區(qū)域是在虛擬機(jī)啟動時創(chuàng)建的。雖然方法區(qū)域在邏輯上是堆的一部分,但是簡單的實現(xiàn)可能選擇不垃圾收集或壓縮它。本規(guī)范未規(guī)定方法區(qū)域的位置或用于管理編譯代碼的策略。方法區(qū)域可以是固定大小的,或者可以根據(jù)計算的需要進(jìn)行擴(kuò)展,并且如果不需要更大的方法區(qū)域,則可以縮小方法區(qū)域。方法區(qū)域的內(nèi)存不需要是連續(xù)的。
虛擬機(jī)的體系結(jié)構(gòu):①Java棧,② 堆,③PC寄存器,④方法區(qū),⑤本地方法棧,⑥運(yùn)行常量池
本地方法棧
Native method stack(本地方法棧):保存native方法進(jìn)入?yún)^(qū)域的地址。
A frame is used to store data and partial results, as well as to perform dynamic linking, return values for methods, and dispatch exceptions.
"frame" 幀用于`存儲數(shù)據(jù)和部分結(jié)果`,以及`執(zhí)行動態(tài)鏈接`,`返回方法的值`以及`調(diào)度異常`。
幀是用于支持虛擬機(jī)進(jìn)行方法調(diào)用和方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu),是虛擬機(jī)運(yùn)行時數(shù)據(jù)區(qū)的虛擬機(jī)棧的元素。
棧幀存儲了方法的局部變量表,操作數(shù)棧,動態(tài)連接和方法返回地址等信息。
每次調(diào)用方法時都會創(chuàng)建一個新幀。當(dāng)方法調(diào)用完成時,幀將被銷毀,無論該方法是正常完成還是異常退出。
jvm為每個新創(chuàng)建的線程都分配一個棧。棧是以幀為單位保存線程的狀態(tài)。jvm對棧只進(jìn)行兩種操作:以幀為單位的壓棧和出棧操作。
幀是從創(chuàng)建幀的線程的Java虛擬機(jī)棧中分配的。每個幀都有自己的局部變量數(shù)組、自己的操作數(shù)棧、以及對當(dāng)前方法的類的運(yùn)行時常量池的引用。
棧幀存儲了方法的局部變量表,操作數(shù)棧,動態(tài)鏈接和方法返回地址等信息。一個方法從調(diào)用開始到執(zhí)行完成,就對應(yīng)著一個棧幀在虛擬機(jī)棧中從入棧到出棧的過程。
在編譯代碼的時候,棧幀中需要多大的局部變量表,多深的操作數(shù)棧 都已經(jīng)完全確定了,并且寫入到了方法表的Code屬性中,因此一個棧幀需要分配多少內(nèi)存,不會受到程序運(yùn)行期變量數(shù)據(jù)的影響,而僅僅取決于具體虛擬機(jī)的實現(xiàn)
一個線程中的方法調(diào)用鏈可能會很長,很多方法都同時處理執(zhí)行狀態(tài)。對于執(zhí)行引擎來講,活動線程中,只有虛擬機(jī)棧頂?shù)臈攀怯行У?,稱為當(dāng)前棧幀 (Current Stack Frame),這個棧幀所關(guān)聯(lián)的方法稱為當(dāng)前方法(Current Method)。執(zhí)行引用所運(yùn)行的所有字節(jié)碼指令都只針對當(dāng)前棧幀進(jìn)行操作。
jvm棧保存局部變量和部分結(jié)果,并在方法調(diào)用和返回中起作用、
jvm棧里存放的棧幀、jvm棧的內(nèi)存不需要是連續(xù)的、jvm棧只允許push and pop frames。
"局部變量表" Local Variables
每個幀包含一個稱為局部變量的變量數(shù)組。
幀的局部變量數(shù)組的長度在編譯時確定,并以類或接口的二進(jìn)制表示形式提供,同時提供與幀相關(guān)的方法的代碼。
單個局部變量可以包含boolean,byte,char,short,int,float,reference或returnAddress類型的值。一對局部變量可以包含long或double類型的值。
"操作數(shù)棧" Operand Stacks
每個幀包含一個后進(jìn)先出(LIFO)棧,稱為其操作數(shù)堆棧。
同局部變量表一樣,操作數(shù)棧的最大深度也是編譯的時候被寫入到方法表的Code屬性的 max_stacks數(shù)據(jù)項中。
"動態(tài)連接" Dynamic Linking
每個棧幀都包含一個指向運(yùn)行時常量池中該棧幀所屬性方法的引用,持有這個引用是為了支持方法調(diào)用過程中的動態(tài)連接。
在Class文件的常量池中存有大量的 符號引用,字節(jié)碼中的方法調(diào)用指令就以常量池中指向方法的符號引用為參數(shù)。
這些符號引用一部分會在類加載階段或第一次使用的時候轉(zhuǎn)化為直接引用,這種轉(zhuǎn)化 稱為靜態(tài)解析。另外一部分將在每一次的運(yùn)行期期間轉(zhuǎn)化為直接引用,這部分稱為動態(tài)連接。
動態(tài)鏈接將這些符號方法引用轉(zhuǎn)換為具體的方法引用,根據(jù)需要加載類以解析尚未定義的符號,并將變量訪問轉(zhuǎn)換為與這些變量的運(yùn)行時位置相關(guān)聯(lián)的存儲結(jié)構(gòu)中的適當(dāng)偏移。
"ClassLoader" 是負(fù)責(zé)加載class文件,class文件在文件開頭有特定的文件標(biāo)示,并且ClassLoader只負(fù)責(zé)class文件的加載,至于它是否可以運(yùn)行,則由Execution Engine決定
"Native Interface" 是負(fù)責(zé)調(diào)用本地接口的。他的作用是調(diào)用不同語言的接口給JAVA用,他會在Native Method Stack中記錄對應(yīng)的本地方法,然后調(diào)用該方法時就通過Execution Engine加載對應(yīng)的本地lib。原本多于用一些專業(yè)領(lǐng)域,如JAVA驅(qū)動,地圖制作引擎等,現(xiàn)在關(guān)于這種本地方法接口的調(diào)用已經(jīng)被類似于Socket通信,WebService等方式取代
"Execution Engine" 是執(zhí)行引擎,也叫Interpreter。Class文件被加載后,會把指令和數(shù)據(jù)信息放入內(nèi)存中,Execution Engine則負(fù)責(zé)把這些命令解釋給操作系統(tǒng)
"Runtime Data Area" 則是存放數(shù)據(jù)的,分為五部分:Stack,Heap,Method Area,PC Register,Native Method Stack。幾乎所有的關(guān)于java內(nèi)存方面的問題,都是集中在這塊