此處的內(nèi)容是根據(jù)Java虛擬機(jī)規(guī)范(Java SE 7)相關(guān)內(nèi)容以及深入理解Java虛擬機(jī)等做的總結(jié)??赡苡胁粚?duì)的地方。了解這些區(qū)域,可以從總體上看下虛擬機(jī)內(nèi)部是怎么構(gòu)造的,網(wǎng)上也有相關(guān)的圖片介紹,可以適當(dāng)?shù)挠浵聢D片內(nèi)容,這樣可以有一個(gè)立體的感受,更容易記憶。
Java虛擬機(jī)定義了程序運(yùn)行期間使用到的運(yùn)行時(shí)數(shù)據(jù)區(qū)域,其中一些與虛擬機(jī)生命周期相同,另外一些與線程的生命周期相同。JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)域分為:
- 程序計(jì)數(shù)器(Program Counter)
- Java虛擬機(jī)棧(Java Virtual Machine Stack)
- 堆(Heap)
- 方法區(qū)(Method Area)
- 本地方法棧(Native Method Stack)
程序計(jì)數(shù)器(Program Counter)
程序計(jì)數(shù)器是線程私有的,每條線程都有自己的程序計(jì)數(shù)器。
Java虛擬機(jī)是支持多線程的,多線程是通過線程的輪流切換來實(shí)現(xiàn)的,也就是說每次切換都需要在上次停頓的地方重新開始運(yùn)行,這時(shí)候就需要程序計(jì)數(shù)器來保存當(dāng)前線程正在執(zhí)行的字節(jié)碼指令的地址,切換到該線程的時(shí)候,就能知道該執(zhí)行哪一個(gè)字節(jié)碼指令了。
如果一個(gè)線程正在執(zhí)行的方法是Java方法,程序計(jì)數(shù)器保存的是Java虛擬機(jī)正在執(zhí)行的字節(jié)碼指令的地址;如果正在執(zhí)行的方法是native的,程序計(jì)數(shù)器的值為undefined。
Java虛擬機(jī)棧(Java Virtual Machine Stack)
Java虛擬機(jī)棧也是線程私有的,與線程同時(shí)創(chuàng)建,用于存儲(chǔ)棧幀(Fremas),棧幀用來存儲(chǔ)局部變量,操作數(shù)棧、指向當(dāng)前方法所屬類的運(yùn)行時(shí)常量池、處理動(dòng)態(tài)鏈接、方法返回值和異常分派。方法從調(diào)用到執(zhí)行完成的過程就對(duì)應(yīng)著一個(gè)棧幀從入棧到出棧的過程。
Java虛擬機(jī)??梢员粚?shí)現(xiàn)為固定大小的,此時(shí)每一條線程的Java虛擬機(jī)棧在線程創(chuàng)建的時(shí)候容量就已經(jīng)確定;還可以被實(shí)現(xiàn)為根據(jù)計(jì)算動(dòng)態(tài)擴(kuò)展和收縮的。
Java虛擬機(jī)??赡軙?huì)發(fā)生異常:
- 如果線程請(qǐng)求的棧容量超過Java虛擬機(jī)棧允許的最大容量,會(huì)拋出StackOverflowError異常。
- 如果虛擬機(jī)??蓜?dòng)態(tài)擴(kuò)展,申請(qǐng)不到足夠的內(nèi)存去完成擴(kuò)展,或者建立新線程時(shí)沒有足夠的內(nèi)存去創(chuàng)建虛擬機(jī)棧,會(huì)拋出OutOfMemoryError異常。
棧幀
棧幀隨著方法的調(diào)用而創(chuàng)建,隨著方法的結(jié)束(正?;蛘弋惓=Y(jié)束)而銷毀,是用來存儲(chǔ)數(shù)據(jù)和部分過程結(jié)果的數(shù)據(jù)結(jié)構(gòu),同時(shí)也被用來處理動(dòng)態(tài)鏈接(Dynamic Linking)、方法返回值和異常分派(Dispatch Exception)。
棧幀存在于Java虛擬機(jī)棧中,棧幀中包含局部變量表、操作數(shù)棧和指向當(dāng)前方法所屬類的運(yùn)行時(shí)常量池的引用。
局部變量表和操作數(shù)棧的容量在編譯期確定,通過方法的Code屬性保存并提供給棧幀使用。棧幀的容量大小僅僅取決于Java虛擬機(jī)的實(shí)現(xiàn)和方法調(diào)用時(shí)可分配的內(nèi)存。
局部變量表
局部變量表存在于棧幀中,長度在編譯期決定,存儲(chǔ)在類和接口的二進(jìn)制表示中,也就是存儲(chǔ)在方法的Code屬性中并提供給棧幀使用。
局部變量表可以保存類型為boolean、byte、char、short、int、float、reference、returnAddress,而long和double類型需要兩個(gè)局部變量表來存儲(chǔ)。
局部變量表還用來完成方法調(diào)用時(shí)參數(shù)的傳遞,一個(gè)方法被調(diào)用,它的參數(shù)會(huì)傳遞至0開始的連續(xù)局部變量表位置上。對(duì)于實(shí)例方法來說,局部變量表第0個(gè)位置是用來存儲(chǔ)實(shí)例方法所在對(duì)象的引用,也就是我們通常說的this。
操作數(shù)棧
操作數(shù)棧存在于棧幀中,是一個(gè)LIFO的棧,長度由編譯期確定,也是存儲(chǔ)在方法的Code屬性中提供給棧幀使用。
操作數(shù)棧會(huì)有一個(gè)確定的棧深度,一個(gè)long或者double類型的數(shù)據(jù)會(huì)占用兩個(gè)單位的棧深度,其他數(shù)據(jù)類型則會(huì)占用一個(gè)單位深度。
動(dòng)態(tài)鏈接
棧幀內(nèi)部包含一個(gè)指向運(yùn)行時(shí)常量池的引用(運(yùn)行時(shí)常量池的解釋在下面,可以先看一下運(yùn)行時(shí)常量池),這個(gè)引用用來支持當(dāng)前方法的代碼實(shí)現(xiàn)動(dòng)態(tài)鏈接。
Class文件中,一個(gè)方法調(diào)用其他方法或者訪問其成員變量是通過符號(hào)引用來表示的,動(dòng)態(tài)鏈接作用就是將符號(hào)引用轉(zhuǎn)換為實(shí)際的直接引用。
堆(Heap)
堆是各個(gè)線程共享的運(yùn)行時(shí)內(nèi)存區(qū)域,也是所有的類實(shí)例和數(shù)組對(duì)象分配內(nèi)存的區(qū)域。堆在虛擬機(jī)啟動(dòng)的時(shí)候被創(chuàng)建,存儲(chǔ)了被垃圾收集器所管理的各種對(duì)象。
堆的容量可以是固定大小的,也可以是動(dòng)態(tài)擴(kuò)展和自動(dòng)收縮的。Java堆的內(nèi)存不需要保證是連續(xù)的。
Java堆可能發(fā)生異常情況:
- 實(shí)際所需的堆超過了最大容量,拋出OutOfMemoryError異常。
方法區(qū)(Method Area)
方法區(qū)也是被各個(gè)線程所共享的運(yùn)行時(shí)內(nèi)存區(qū)域。用于存儲(chǔ)類的結(jié)構(gòu)信息,例如運(yùn)行時(shí)常量池、字段、方法數(shù)據(jù)、構(gòu)造函數(shù)、普通方法的字節(jié)碼內(nèi)容、還包括一些在類、實(shí)例、接口初始化時(shí)用到的特殊方法。
方法區(qū)在虛擬機(jī)啟動(dòng)的時(shí)候被創(chuàng)建,是堆的邏輯組成部分。方法區(qū)的容量可以是固定大小的,也可以是動(dòng)態(tài)擴(kuò)展和自動(dòng)收縮的。內(nèi)存空間不需要保證是連續(xù)的。
方法區(qū)可能發(fā)生異常的情況:
- 方法區(qū)的內(nèi)存不能滿足內(nèi)存分配時(shí),會(huì)拋出OutOfMemoryError異常。
運(yùn)行時(shí)常量池
運(yùn)行時(shí)常量池分配在方法區(qū)中,類和接口被加載到虛擬機(jī)之后,運(yùn)行時(shí)常量池就被創(chuàng)建了。
運(yùn)行時(shí)常量池是類或接口的常量池的運(yùn)行時(shí)表示形式,包括從編譯期可知的數(shù)值字面量和運(yùn)行期解析后才能獲得的方法或字段引用。
可能會(huì)發(fā)生異常的情況:
- 構(gòu)造運(yùn)行時(shí)常量池所需的內(nèi)存空間超過了方法區(qū)能提供的最大值,會(huì)拋出OutOfMemoryError異常。
本地方法棧(Native Method Stack)
用來支持native方法。跟虛擬機(jī)棧功能類似。本地方法棧被實(shí)現(xiàn)成固定大小或者是動(dòng)態(tài)擴(kuò)展和收縮的。
可能會(huì)發(fā)生的異常情況:
- 如果線程請(qǐng)求的棧容量超過本地方法棧允許的最大容量,會(huì)拋出StackOverflowError異常。
- 如果本地方法??蓜?dòng)態(tài)擴(kuò)展,申請(qǐng)不到足夠的內(nèi)存去完成擴(kuò)展,或者建立新線程時(shí)沒有足夠的內(nèi)存去創(chuàng)建對(duì)應(yīng)的本地方法棧,會(huì)拋出OutOfMemoryError異常。
簡要總結(jié)
程序計(jì)數(shù)器為線程私有,用來指示程序運(yùn)行時(shí)的位置。
Java虛擬機(jī)棧是線程私有的,用來存儲(chǔ)局部變量表等,出棧入棧對(duì)應(yīng)著方法的結(jié)束開始。
堆是線程共享的區(qū)域,虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建,創(chuàng)建的實(shí)例對(duì)象和數(shù)組都分配在堆上。
方法區(qū)是線程共享的區(qū)域,虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建,用來存儲(chǔ)類的信息,常量字段等等。
本地方法棧用來執(zhí)行本地方法的。