JVM规范系列第2章:Java虚拟机结构
本規(guī)范描述的是一種抽象化的虛擬機(jī)的行為,而不是任何一種(譯者注:包括 Oracle 公司自己的 HotSpot 和 JRockit 虛擬機(jī))被廣泛使用的虛擬機(jī)實(shí)現(xiàn)。
記住:JVM規(guī)范是一種高度抽象行為的描述,而不是具體虛擬機(jī)的實(shí)現(xiàn)。
所有在虛擬機(jī)規(guī)范之中沒(méi)有明確描述的實(shí)現(xiàn)細(xì)節(jié),都不應(yīng)成為虛擬機(jī)設(shè)計(jì)者發(fā)揮創(chuàng)造性的牽絆,設(shè)計(jì)者可以完全自主決定所有規(guī)范中不曾描述的虛擬機(jī)內(nèi)部細(xì)節(jié),例如:運(yùn)行時(shí)數(shù)據(jù)區(qū)的內(nèi)存如何布局、選用哪種垃圾收集的算法、是否要對(duì)虛擬機(jī)字節(jié)碼指令進(jìn)行一些內(nèi)部?jī)?yōu)化操作(如使用即時(shí)編譯器把字節(jié)碼編譯為機(jī)器碼)。
簡(jiǎn)單地說(shuō),就是《JVM規(guī)范》中提到的內(nèi)容,你一定要遵守。但沒(méi)有提到的內(nèi)容,你就自由發(fā)揮。所以我們要知道,我們經(jīng)常聽(tīng)到的老年代、年輕代、永久代,其實(shí)只是HotSpot虛擬機(jī)的實(shí)現(xiàn)而已。因?yàn)椤禞VM規(guī)范》中并沒(méi)有規(guī)定這些東西。
編譯后被 Java 虛擬機(jī)所執(zhí)行的代碼使用了一種平臺(tái)中立(不依賴于特定硬件及操作系統(tǒng)的)的二進(jìn)制格式來(lái)表示,并且經(jīng)常(但并非絕對(duì))以文件的形式存儲(chǔ),因此這種格式被稱為 Class文件格式。
實(shí)際上字節(jié)碼文件,即Class文件格式使用十六進(jìn)制編碼,使用二進(jìn)制格式存儲(chǔ)。
Class 文件格式中精確地定義了類與接口的表示形式, 包括在平臺(tái)相關(guān)的目標(biāo)文件格式中一些細(xì)節(jié)上的慣例①,例如字節(jié)序(Byte Ordering)等。
這里又一個(gè)計(jì)算機(jī)的基礎(chǔ)概念:字節(jié)序。有時(shí)間可以百度一下搞清楚這個(gè)概念。當(dāng)然了,這個(gè)并不會(huì)影響對(duì)于JVM的理解??梢詤⒖歼@篇文章:https://www.cnblogs.com/broglie/p/5645200.html
JVM的數(shù)據(jù)類型
與 Java 程序語(yǔ)言中的數(shù)據(jù)類型相似,Java 虛擬機(jī)可以操作的數(shù)據(jù)類型可分為兩類:原始類型(Primitive Types,也經(jīng)常翻譯為原生類型或者基本類型)和引用類型(Reference Types)。與之對(duì)應(yīng),也存在有原始值(Primitive Values)和引用值(Reference Values)兩種類型的數(shù)值。
JVM中也有兩種類型:原始類型、引用類型。
Java 虛擬機(jī)希望盡可能多的類型檢查能在程序運(yùn)行之前完成,換句話說(shuō),編譯器應(yīng)當(dāng)在編譯
期間盡最大努力完成可能的類型檢查,使得虛擬機(jī)在運(yùn)行期間無(wú)需進(jìn)行這些操作。
從這句話我們知道:在編譯期間是會(huì)及逆行類型檢查的,通過(guò)在編譯進(jìn)行類型檢查,減少JVM運(yùn)行時(shí)的工作量,提高效率。
Java 虛擬機(jī)所支持的原始數(shù)據(jù)類型包括了數(shù)值類型(Numeric Types)、 布爾類型(Boolean Type § 2.3.4) 和 returnAddress 類型(§ 2.3.3) 三類。其中數(shù)值類型又分為整型類型(Integral Types, § 2.3.1)和浮點(diǎn)類型(Floating-Point Types, § 2.3.2)兩種。
和Java語(yǔ)言不同,JVM的原始類型包括:數(shù)值類型、布爾類型、returnAddress類型。
- 數(shù)值類型。又分為整數(shù)類型和浮點(diǎn)類型。整數(shù)類型包括:byet、short、int、long、char。浮點(diǎn)類型包括:float類型、double類型。
- 布爾類型。包括:boolean類型,取值為true和false,默認(rèn)為false。
- returnAddress類型。表示一條字節(jié)碼指令的操作碼。
可以看到在所有的虛擬機(jī)支持的原始類型之中,只有 returnAddress 類型是不能直接 Java 語(yǔ)言的數(shù)據(jù)類型對(duì)應(yīng)起來(lái)的。
整數(shù)類型以及整型值
Java 虛擬機(jī)中的整型類型的取值范圍如下:
對(duì)于 byte 類型,取值范圍是從-128 至 127(-27至 27-1),包括-128 和 127。
對(duì)于 short 類型,取值范圍是從?32768 至 32767(-215至 215-1),包括?32768 和 32767。
對(duì)于 int 類型,取值范圍是從?2147483648 至 2147483647(-231至 231-1),包括?2147483648 和 2147483647。
對(duì)于 long 類型,取值范圍是從?9223372036854775808 至 9223372036854775807(-263至 263-1),包括?9223372036854775808 和 9223372036854775807。
對(duì)于 char 類型, 取值范圍是從 0 至 65535,包括 0 和 65535。
浮點(diǎn)類型的取值及范圍
浮點(diǎn)類型包含 float 類型和 double 類型兩種,它們?cè)诟拍钌吓c《IEEE Standard for Binary Floating-Point Arithmetic》 ANSI/IEEE Std. 754-1985(IEEE, New York)標(biāo)準(zhǔn)中定義的 32 位單精度和 64 位雙精度 IEEE 754 格式取值和操作都是一致的。
浮點(diǎn)類型包括了 float 類型和 double 類型,它們是在 IEEE 754 標(biāo)準(zhǔn)中定義的。
所有 Java 虛擬機(jī)的實(shí)現(xiàn)都必須支持兩種標(biāo)準(zhǔn)的浮點(diǎn)數(shù)值集合:單精度浮點(diǎn)數(shù)集合和雙精度浮點(diǎn)數(shù)集合。
所有虛擬機(jī)都必須支持單精度浮點(diǎn)數(shù)集合和雙精度浮點(diǎn)數(shù)集合。
關(guān)于浮點(diǎn)數(shù)的內(nèi)容,其實(shí)看起來(lái)會(huì)非常暈。所以我們暫時(shí)可以大致看一下就可以,畢竟關(guān)于浮點(diǎn)數(shù)如何定義又是另一個(gè)標(biāo)準(zhǔn)了。
returnAddress 類型
returnAddress 類型會(huì)被 Java 虛擬機(jī)的 jsr、ret 和 jsr_w 指令所使用。jsr、ret、jsr_w 這幾條指令以前主要被使用來(lái)實(shí)現(xiàn) finally 語(yǔ)句塊,后來(lái)改為冗余 finally 塊代碼的方式來(lái)實(shí)
現(xiàn),甚至到了 JDK7 時(shí),虛擬機(jī)已不允許 Class 文件內(nèi)出現(xiàn)這幾條指令。那相應(yīng)地, returnAddress 類型就處于名存實(shí)亡的狀態(tài)。
簡(jiǎn)單地說(shuō),returnAddress類型現(xiàn)在已經(jīng)算是被拋棄了,所以我們只需要大致了解一下有這個(gè)東西就好了。
boolean類型
雖然 Java 虛擬機(jī)定義了 boolean 這種數(shù)據(jù)類型,但是只對(duì)它提供了非常有限的支持。在Java 虛擬機(jī)中沒(méi)有任何供 boolean 值專用的字節(jié)碼指令,在 Java 語(yǔ)言之中涉及到 boolean類型值的運(yùn)算,在編譯之后都使用 Java 虛擬機(jī)中的 int 數(shù)據(jù)類型來(lái)代替。
可以說(shuō),我們學(xué)會(huì)了int類型,就學(xué)會(huì)了boolean類型。因?yàn)閎oolean類型就是簡(jiǎn)化版的int類型。boolean的虛擬機(jī)指令集都是使用int類型的指令集。
引用類型與值
Java 虛擬機(jī)中有三種引用類型:類類型(Class Types)、數(shù)組類型(Array Types)和接口類型(Interface Types)。這些引用類型的值分別由類實(shí)例、數(shù)組實(shí)例和實(shí)現(xiàn)了某個(gè)接口的類實(shí)例或數(shù)組實(shí)例動(dòng)態(tài)創(chuàng)建。
不僅原始類型不同,JVM的引用類型與原始類型也是不同的。JVM的引用類型有三種:
- 類類型。對(duì)應(yīng)的值是類實(shí)例。
- 數(shù)組類型。對(duì)應(yīng)的值時(shí)數(shù)組實(shí)例。
- 接口類型。對(duì)應(yīng)的值時(shí)實(shí)現(xiàn)了某個(gè)接口的類實(shí)例或數(shù)組實(shí)例。
嗯,美滋滋啊。又學(xué)到新東西嘞。
數(shù)組類型還包含一個(gè)單一維度(即長(zhǎng)度不由其類型決定)的組件類型(Component Type),一個(gè)數(shù)組的組件類型也可以是數(shù)組。但從任意一個(gè)數(shù)組開(kāi)始,如果發(fā)現(xiàn)其組件類型也是數(shù)組類型的話,繼續(xù)重復(fù)取這個(gè)數(shù)組的組件類型,這樣操作不斷執(zhí)行,最終一定可以遇到組件類型不是數(shù)組的情況,這時(shí)就把這種類型成為數(shù)組類型的元素類型(Element Type)。數(shù)組的元素類型必須是原始類型、類類型或者接口類型之中的一種。
這段話很繞,其中有幾個(gè)關(guān)鍵詞:數(shù)組類型、組件類型(Component Type)。其實(shí)這段話意思應(yīng)該是說(shuō)數(shù)組可以有多維的意思,而數(shù)組里的類型又可以是其他各種類型。大致意思應(yīng)該是如此,但我也不是100%確定。這里MARK一下。TODO
在引用類型的值中還有一個(gè)特殊的值: null,當(dāng)一個(gè)引用不指向任何對(duì)象的時(shí)候,它的值就用 null 來(lái)表示。Java 虛擬機(jī)規(guī)范并沒(méi)有規(guī)定 null 在虛擬機(jī)實(shí)現(xiàn)中應(yīng)當(dāng)怎樣編碼表示。
關(guān)于null的定義,了解一下。
運(yùn)行時(shí)數(shù)據(jù)區(qū)
終于到運(yùn)行時(shí)數(shù)據(jù)區(qū)了,其實(shí)這塊就是我們經(jīng)常說(shuō)的JVM內(nèi)存模型這些東西。但實(shí)際上JVM規(guī)范中并沒(méi)有這個(gè)術(shù)語(yǔ)。JVM規(guī)范只定義了「運(yùn)行時(shí)數(shù)據(jù)區(qū)」這個(gè)術(shù)語(yǔ),指的就是JVM運(yùn)行時(shí)其內(nèi)存的數(shù)據(jù)區(qū)是怎么樣的,應(yīng)該包含哪些東西。具體怎么實(shí)現(xiàn),你們各個(gè)虛擬機(jī)自己打算去。
Java 虛擬機(jī)定義了若干種程序運(yùn)行期間會(huì)使用到的運(yùn)行時(shí)數(shù)據(jù)區(qū),其中有一些會(huì)隨著虛擬機(jī)啟動(dòng)而創(chuàng)建,隨著虛擬機(jī)退出而銷毀。另外一些則是與線程一一對(duì)應(yīng)的,這些與線程對(duì)應(yīng)的數(shù)據(jù)區(qū)域會(huì)隨著線程開(kāi)始和結(jié)束而創(chuàng)建和銷毀。
為什么我們經(jīng)常用JVM內(nèi)存模型來(lái)概括運(yùn)行時(shí)數(shù)據(jù)區(qū),就是因?yàn)檫\(yùn)行時(shí)數(shù)據(jù)區(qū)說(shuō)的概念太過(guò)于分散,沒(méi)有聯(lián)系,所以才會(huì)有JVM內(nèi)存模型這個(gè)詞,讓我們把這些東西聯(lián)系起來(lái),方便記憶。
從上面這段話,我們可以進(jìn)行一些概括。首先第一句說(shuō)到:
Java 虛擬機(jī)定義了若干種程序運(yùn)行期間會(huì)使用到的運(yùn)行時(shí)數(shù)據(jù)區(qū),其中有一些會(huì)隨著虛擬機(jī)啟動(dòng)而創(chuàng)建,隨著虛擬機(jī)退出而銷毀。
意思是說(shuō)有些東西會(huì)隨著虛擬機(jī)啟動(dòng)而一直存在,而隨著虛擬機(jī)退出而銷毀。而另外一句:
另外一些則是與線程一一對(duì)應(yīng)的,這些與線程對(duì)應(yīng)的數(shù)據(jù)區(qū)域會(huì)隨著線程開(kāi)始和結(jié)束而創(chuàng)建和銷毀。
上面這句意思是說(shuō),又有些數(shù)據(jù)是隨著線程變化的。
其實(shí)總結(jié)一下就是:JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)有些數(shù)據(jù)是一直存在的,被所有線程共享的。而有些線程則是線程私有的,隨著線程開(kāi)始而創(chuàng)建,結(jié)束而銷毀。所以,我們可以將運(yùn)行時(shí)數(shù)據(jù)區(qū)的東西簡(jiǎn)單分為兩類:一類是公有的,一類是私有的。
通過(guò)這樣一歸類,你對(duì)于運(yùn)行時(shí)數(shù)據(jù)區(qū)的概念是不是清晰了許多呢!
可能你還沒(méi)感覺(jué)到,那是因?yàn)榈竭@里你還不知道運(yùn)行時(shí)數(shù)據(jù)區(qū)到底有多少個(gè)東西。在這里我先列一下。運(yùn)行時(shí)數(shù)據(jù)區(qū)包括下面幾個(gè)部分:
- PC寄存器
- Java虛擬機(jī)棧
- Java堆
- 方法區(qū)
- 運(yùn)行時(shí)常量池
- 本地方法棧
好了。記住這 6 個(gè)東西還真是困難。那我們用我們上面說(shuō)的,加個(gè)公有私有的分類試試看。
- 公有部分包括:Java堆、方法區(qū)、運(yùn)行時(shí)常量池。
- 私有部分包括:Java虛擬機(jī)棧、本地方法棧、PC寄存器。
怎么樣,這樣一歸類,我相信更容易記住了。而且也更符合我們對(duì)JVM的理解。JVM的運(yùn)行時(shí)數(shù)據(jù)區(qū)有哪些東西?
首先,有公有和私有兩個(gè)部分,公有包括……私有包括……。
大腦天然喜歡結(jié)構(gòu)化的數(shù)據(jù),這種方法才是理解運(yùn)行時(shí)數(shù)據(jù)區(qū)的正確姿勢(shì)。
說(shuō)得有點(diǎn)多了,我們接下來(lái)繼續(xù)讀JVM規(guī)范。
PC寄存器
Java 虛擬機(jī)可以支持多條線程同時(shí)執(zhí)行(可參考《Java 語(yǔ)言規(guī)范》第 17 章),每一條 Java虛擬機(jī)線程都有自己的 PC(Program Counter)寄存器。在任意時(shí)刻,一條 Java 虛擬機(jī)線程只會(huì)執(zhí)行一個(gè)方法的代碼,這個(gè)正在被線程執(zhí)行的方法稱為該線程的當(dāng)前方法。
從這段描述驗(yàn)證上面我的理解是對(duì)的。PC寄存器就是線程私有的,每個(gè)線程都有一個(gè)PC寄存器。而PC寄存器是用來(lái)存儲(chǔ)當(dāng)前線程所執(zhí)行方法的地址。
Java虛擬機(jī)棧
每一條 Java 虛擬機(jī)線程都有自己私有的 Java 虛擬機(jī)棧(Java Virtual Machine Stack)①,這個(gè)棧與線程同時(shí)創(chuàng)建,用于存儲(chǔ)棧幀(Frames, § 2.6)。
從這段描述我們同樣可以看到,Java虛擬機(jī)棧同樣也是線程私有的。Java虛擬機(jī)棧的作用就是用來(lái)存儲(chǔ)「棧幀」。棧幀這個(gè)概念也非常重要,其存儲(chǔ)了調(diào)用方法時(shí),方法的局部變量等信息。后續(xù)會(huì)深入學(xué)習(xí)「棧幀」這個(gè)概念,這里不做深入介紹。
如果線程請(qǐng)求分配的棧容量超過(guò) Java 虛擬機(jī)棧允許的最大容量時(shí), Java 虛擬機(jī)將會(huì)拋出一個(gè) StackOverflowError 異常。
傳說(shuō)中的StackOverflowError就是因?yàn)檫@塊區(qū)域出現(xiàn)了問(wèn)題。
如果 Java 虛擬機(jī)??梢詣?dòng)態(tài)擴(kuò)展,并且擴(kuò)展的動(dòng)作已經(jīng)嘗試過(guò),但是目前無(wú)法申請(qǐng)到足夠的內(nèi)存去完成擴(kuò)展,或者在建立新的線程時(shí)沒(méi)有足夠的內(nèi)存去創(chuàng)建對(duì)應(yīng)的虛擬機(jī)棧,那 Java 虛擬機(jī)將會(huì)拋出一個(gè) OutOfMemoryError 異常。
這里很有意思。意思是說(shuō)OutOfMemoryError異常,有可能是因?yàn)镾tack不夠?qū)е碌摹?/p>
我相信許多對(duì)于JVM理解不夠深刻的朋友看到這里會(huì)很疑惑,因?yàn)樗麄儗?duì)于JVM內(nèi)存模型的理解就是:Java堆、Java棧。但實(shí)際上并不是這樣的。
你應(yīng)該這樣理解:首先,你得知道「運(yùn)行時(shí)數(shù)據(jù)區(qū)」,就是JVM運(yùn)行時(shí)的這塊內(nèi)存。之后,你要知道這塊區(qū)域分為兩個(gè)部分:公有和私有。而Java虛擬機(jī)棧是分配在堆上的(注意,并不是Java堆),但分配出來(lái)的這塊內(nèi)存是線程私有的。
在創(chuàng)建線程是申請(qǐng)創(chuàng)建Java虛擬機(jī)棧,可能有兩種情況。第一種,堆上的內(nèi)存不夠了,那么無(wú)法創(chuàng)建Java虛擬機(jī)棧。這就是上面說(shuō)到的這種情況,會(huì)發(fā)生OutOfMemoryError異常。另一種情況,就是內(nèi)存申請(qǐng)成功了,但是在線程創(chuàng)建后,調(diào)用方法的層次太深了,之前申請(qǐng)的這部分內(nèi)存不夠用了。這時(shí)候會(huì)發(fā)生StackOverflowError。
說(shuō)到這兒,你會(huì)發(fā)現(xiàn)在《JVM規(guī)范》第28頁(yè)末尾寫了這么一段話:
譯者注:請(qǐng)讀者注意避免混淆 Stack、 Heap 和 Java (VM) Stack、 Java Heap 的概念, Java 虛擬機(jī)的實(shí)現(xiàn)本身是由其他語(yǔ)言編寫的應(yīng)用程序,在 Java 語(yǔ)言程序的角度上看分配在 Java Stack 中的數(shù)據(jù),而在實(shí)現(xiàn)虛擬機(jī)的程序角度上看則可以是分配在 Heap 之中。
這段話真是不太好理解,但實(shí)際上沒(méi)那么難。首先,我們的JVM是使用其他語(yǔ)言寫的,那么這個(gè)要運(yùn)行的時(shí)候是不是要像系統(tǒng)申請(qǐng)內(nèi)存,那么申請(qǐng)的這一大塊內(nèi)存就是堆內(nèi)存。
在這么大一塊內(nèi)存里,我們又分一部分作為Java堆內(nèi)存、Java虛擬機(jī)棧的內(nèi)存。也即是說(shuō),JVM中的堆內(nèi)存是系統(tǒng)的堆內(nèi)存中的堆內(nèi)存。站在實(shí)現(xiàn)JVM的程序來(lái)說(shuō),是分配在堆上的。而站在Java程序的角度上看,是分配在Java堆上的。也就是說(shuō)可以想象這樣一個(gè)圖表:
- 系統(tǒng)內(nèi)存有堆內(nèi)存、棧內(nèi)存之分。
- JVM啟動(dòng),向系統(tǒng)申請(qǐng)一塊內(nèi)存,系統(tǒng)會(huì)分配一塊堆內(nèi)存給它。當(dāng)其他應(yīng)用程序,例如微信啟動(dòng),系統(tǒng)也會(huì)分配一塊堆內(nèi)存給它。
- JVM獲得了內(nèi)存,于是加載類、運(yùn)行程序。JVM會(huì)在獲得的內(nèi)存分出一部分內(nèi)存作為Java堆,用于分配對(duì)象。
- 當(dāng)創(chuàng)建線程,那么JVM會(huì)在系統(tǒng)給它的、還未分配的內(nèi)存劃出一部分創(chuàng)建Java虛擬機(jī)棧,從而線程創(chuàng)建成功。
所以如果增加Java堆的大小,那么可以分配給Java虛擬機(jī)棧的內(nèi)存就變少,那么可以創(chuàng)建的線程就減少。那么就更容易導(dǎo)致StackOverflowError。
Java堆
在 Java 虛擬機(jī)中,堆(Heap)是可供各條線程共享的運(yùn)行時(shí)內(nèi)存區(qū)域,也是供所有類實(shí)例和數(shù)組對(duì)象分配內(nèi)存的區(qū)域。
Java堆是所有線程共享的,存放類實(shí)例和數(shù)組對(duì)象。
方法區(qū)
在 Java 虛擬機(jī)中,方法區(qū)(Method Area) 是可供各條線程共享的運(yùn)行時(shí)內(nèi)存區(qū)域。方法區(qū)與傳統(tǒng)語(yǔ)言中的編譯代碼儲(chǔ)存區(qū)(Storage Area Of Compiled Code)或者操作系統(tǒng)進(jìn)程的正文段(Text Segment)的作用非常類似,它存儲(chǔ)了每一個(gè)類的結(jié)構(gòu)信息,例如運(yùn)行時(shí)常量池(Runtime Constant Pool)、字段和方法數(shù)據(jù)、構(gòu)造函數(shù)和普通方法的字節(jié)碼內(nèi)容、還包括一些在類、實(shí)例、接口初始化時(shí)用到的特殊方法(§ 2.9)。
可以看到方法區(qū)也是共享的,而且存放的是類的信息。在HotSpot虛擬機(jī)中,JDK1.7版本稱其為永久代(Permanent Generation),而在JDK1.8則稱之為元空間(Metaspace)。
運(yùn)行時(shí)常量池
每一個(gè)運(yùn)行時(shí)常量池都分配在 Java 虛擬機(jī)的方法區(qū)之中(§ 2.5.4),在類和接口被加載到虛擬機(jī)后,對(duì)應(yīng)的運(yùn)行時(shí)常量池就被創(chuàng)建出來(lái)。
運(yùn)行時(shí)常量池分配在JVM的方法區(qū)之中。
本地方法棧
Java 虛擬機(jī)實(shí)現(xiàn)可能會(huì)使用到傳統(tǒng)的棧(通常稱之為“C Stacks”)來(lái)支持 native 方法(指使用 Java 以外的其他語(yǔ)言編寫的方法)的執(zhí)行,這個(gè)棧就是本地方法棧(Native Method Stack)。當(dāng) Java 虛擬機(jī)使用其他語(yǔ)言(例如 C 語(yǔ)言)來(lái)實(shí)現(xiàn)指令集解釋器時(shí),也會(huì)使用到本地方法棧。
本地方法指的是使用Java以外的其他語(yǔ)言編寫的代碼,因?yàn)橛行r(shí)候Java無(wú)法直接操作一些底層資源,只能通過(guò)C或匯編操作。因此需要通過(guò)本地方法來(lái)實(shí)現(xiàn)。
而本地方法棧就是設(shè)計(jì)用來(lái)調(diào)用這些非Java語(yǔ)言方法的,其作用與Java虛擬機(jī)棧類似。會(huì)存放對(duì)應(yīng)的局部變量信息、返回結(jié)果等。
本地方法棧同樣會(huì)發(fā)生StackOverFlowError和OutOfMemoryError異常。
棧幀
棧幀(Frame)是用來(lái)存儲(chǔ)數(shù)據(jù)和部分過(guò)程結(jié)果的數(shù)據(jù)結(jié)構(gòu),同時(shí)也被用來(lái)處理動(dòng)態(tài)鏈接(Dynamic Linking)、方法返回值和異常分派(Dispatch Exception)。
這里重點(diǎn)記住:存儲(chǔ)數(shù)據(jù)和部分結(jié)果,存儲(chǔ)的部分?jǐn)?shù)據(jù)就包括了局部變量。
棧幀隨著方法調(diào)用而創(chuàng)建,隨著方法結(jié)束而銷毀——無(wú)論方法是正常完成還是異常完成(拋出了在方法內(nèi)未被捕獲的異常)都算作方法結(jié)束。
棧幀隨著方法調(diào)用而創(chuàng)建,是線程私有的。
棧幀的存儲(chǔ)空間分配在 Java 虛擬機(jī)棧(§ 2.5.5)之中,每一個(gè)棧幀都有自己的局部變量表(Local Variables, § 2.6.1)、操作數(shù)棧(Operand Stack, § 2.6.2)和指向當(dāng)前方法所屬的類的運(yùn)行時(shí)常量池(§ 2.5.5)的引用。
棧幀分配在Java虛擬機(jī)棧,而Java虛擬機(jī)棧是私有的,所以棧幀肯定也是私有的??梢赃@么說(shuō),棧幀就是Java虛擬機(jī)棧里的一個(gè)個(gè)元素,每次調(diào)用一個(gè)方法就push一個(gè)棧幀,調(diào)用完畢則poll一個(gè)棧幀。
這里還說(shuō)到,每一個(gè)棧幀都有自己的:
- 局部變量表
- 操作數(shù)棧
- 指向當(dāng)前方法所屬類的運(yùn)行時(shí)常量池引用
在一條線程之中,只有目前正在執(zhí)行的那個(gè)方法的棧幀是活動(dòng)的。這個(gè)棧幀就被稱為是當(dāng)前棧幀(Current Frame),這個(gè)棧幀對(duì)應(yīng)的方法就被稱為是當(dāng)前方法(Current Method),定義這個(gè)方法的類就稱作當(dāng)前類(Current Class)。
了解下「當(dāng)前棧幀」、「當(dāng)前方法」、「當(dāng)前類」的概念。
請(qǐng)讀者特別注意,棧幀是線程本地私有的數(shù)據(jù),不可能在一個(gè)棧幀之中引用另外一條線程的棧幀。
這里原文明確指出:棧幀是線程本地私有的數(shù)據(jù)!
局部變量表
棧幀中局部變量表的長(zhǎng)度由編譯期決定,并且存儲(chǔ)于類和接口的二進(jìn)制表示之中,既通過(guò)方法的Code 屬性(§ 4.7.3)保存及提供給棧幀使用。
局部變量表由編譯期決定,并存在方法的Code屬性。
局部變量使用索引來(lái)進(jìn)行定位訪問(wèn),第一個(gè)局部變量的索引值為零,局部變量的索引值是從零至小于局部變量表最大容量的所有整數(shù)。
局部變量表的索引從零開(kāi)始。
特別地,當(dāng)一個(gè)實(shí)例方法被調(diào)用的時(shí)候,第 0 個(gè)局部變量一定是用來(lái)存儲(chǔ)被調(diào)用的實(shí)例方法所在的對(duì)象的引用(即 Java 語(yǔ)言中的“this”關(guān)鍵字)。
局部變量第一個(gè)局部變量,一定是當(dāng)前對(duì)象的引用,即this關(guān)鍵字。
操作數(shù)棧
每一個(gè)棧幀(§ 2.6)內(nèi)部都包含一個(gè)稱為操作數(shù)棧(Operand Stack)的后進(jìn)先出(Last-In-First-Out, LIFO)棧。
這里要注意一下,Java虛擬機(jī)棧包含了棧幀,棧幀包含了操作數(shù)棧,就像下面這樣的關(guān)系:
Java虛擬機(jī)棧 -> 棧幀 -> 操作數(shù)棧它們都是棧的數(shù)據(jù)結(jié)構(gòu),操作數(shù)棧也是如此。
只不過(guò)Java虛擬機(jī)棧是存儲(chǔ)一個(gè)個(gè)線程的信息,棧幀存儲(chǔ)的事一個(gè)個(gè)方法的信息,操作數(shù)棧是存儲(chǔ)方法調(diào)用中一個(gè)個(gè)操作數(shù)的信息。
每一個(gè)操作數(shù)棧的成員(Entry) 可以保存一個(gè) Java 虛擬機(jī)中定義的任意數(shù)據(jù)類型的值,包括 long 和 double 類型。
操作數(shù)棧的成員可以是任意數(shù)據(jù)類型。
這章節(jié)接下來(lái)的關(guān)于浮點(diǎn)數(shù)、浮點(diǎn)算法部分,太過(guò)于復(fù)雜,而且實(shí)用性太差。這里不深入了解,直接簡(jiǎn)單了解就可以了。
在 Java 虛擬機(jī)層面上, Java 語(yǔ)言中的構(gòu)造函數(shù)在《Java 語(yǔ)言規(guī)范 (第三版)》(下文簡(jiǎn)稱JLS3, § 8.8)是以一個(gè)名為的特殊實(shí)例初始化方法的形式出現(xiàn)的, 這個(gè)方法名稱是由編譯器命名的,因?yàn)樗⒎且粋€(gè)合法的 Java 方法名字,不可能通過(guò)程序編碼的方式實(shí)現(xiàn)。
<init>這個(gè)特殊的初始化方法,是在虛擬機(jī)層面存在的,而不是程序編碼層面的??梢酝ㄟ^(guò)虛擬機(jī)的invokespecial指令調(diào)用。
一個(gè)類或者接口最多可以包含不超過(guò)一個(gè)類或接口的初始化方法,類或者接口就是通過(guò)這個(gè)方法完成初始化的(§ 5.5)。這個(gè)方法是一個(gè)不包含參數(shù)的靜態(tài)方法,名為①。這個(gè)名字也是由編譯器命名的,因?yàn)樗⒎且粋€(gè)合法的 Java 方法名字,不可能通過(guò)程序編碼的方式實(shí)現(xiàn)。
與init類似clinit也是一個(gè)虛擬機(jī)層面的方法,不是程序編碼層面的。但類或接口的clinit方法只能由虛擬機(jī)自身隱式調(diào)用,沒(méi)有任何虛擬機(jī)字節(jié)碼指令可以調(diào)用這個(gè)方法。
字節(jié)碼指令
Java 虛擬機(jī)的指令由一個(gè)字節(jié)長(zhǎng)度的、代表著某種特定操作含義的操作(Opcode)以及跟隨其后的零至多個(gè)代表此操作所需參數(shù)的操作數(shù)(Operands)所構(gòu)成。
字節(jié)碼指令組成為:操作碼+操作數(shù)。其中操作碼為一個(gè)字節(jié)長(zhǎng)度,操作數(shù)情況由操作碼決定。
大部分的指令都沒(méi)有支持整數(shù)類型 byte、 char 和 short,甚至
沒(méi)有任何指令支持 boolean 類型。編譯器會(huì)在編譯期或運(yùn)行期會(huì)將 byte 和 short 類型的數(shù)據(jù)帶符號(hào)擴(kuò)展(Sign-Extend)為相應(yīng)的 int 類型數(shù)據(jù),將 boolean 和 char 類型數(shù)據(jù)零位擴(kuò)展(Zero-Extend)為相應(yīng)的 int 類型數(shù)據(jù)。與之類似的,在處理 boolean、 byte、 short 和char 類型的數(shù)組時(shí),也會(huì)轉(zhuǎn)換為使用對(duì)應(yīng)的 int 類型的字節(jié)碼指令來(lái)處理。
在虛擬機(jī)中,byte/short/boolean/char 都是用 int 類型來(lái)存儲(chǔ)的。規(guī)范的下一句也直接指明了。
因此,大多數(shù)對(duì)于boolean、 byte、 short 和 char 類型數(shù)據(jù)的操作,實(shí)際上都是使用相應(yīng)的對(duì) int 類型作為運(yùn)算類型(Computational Type)。
虛擬機(jī)指令集大致可以分為下面幾類:
- 加載和存儲(chǔ)指令
- 運(yùn)算指令
- 類型轉(zhuǎn)換指令
- 對(duì)象創(chuàng)建于操作
- 操作數(shù)棧管理指令
- 控制轉(zhuǎn)移指令
- 方法調(diào)用和返回指令
- 拋出異常
- 同步
因?yàn)樘摂M機(jī)指令集很枯燥,就像linux命令一樣,所以這里不深入講。有需要的時(shí)候再一個(gè)個(gè)查就可以了。
JVM規(guī)范系列文章目錄
- JVM規(guī)范系列開(kāi)篇:為什么要讀JVM規(guī)范?
- JVM規(guī)范系列第1章:引言
- JVM規(guī)范系列第2章:Java虛擬機(jī)結(jié)構(gòu)
- JVM規(guī)范系列第3章:為Java虛擬機(jī)編譯
- JVM規(guī)范系列第4章:Class文件格式
- JVM規(guī)范系列第5章:加載、鏈接與初始化
- JVM規(guī)范系列第6章:Java虛擬機(jī)指令集
- JVM規(guī)范系列:總結(jié)
轉(zhuǎn)載于:https://www.cnblogs.com/chanshuyi/p/jvm_specification_02_jvm_structure.html
總結(jié)
以上是生活随笔為你收集整理的JVM规范系列第2章:Java虚拟机结构的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 【译】一份通俗易懂的React.js基础
- 下一篇: Java核心技术笔记 语言基础