運(yùn)行時(shí)內(nèi)存區(qū)域劃分
Java源文件(
.java文件
)會(huì)被Java編譯器編譯為字節(jié)碼文件(.class文件
),再由JVM中的類加載器去加載各個(gè)類的字節(jié)碼文件弦疮,加載完成后交給JVM執(zhí)行引擎執(zhí)行夹攒。
在Java程序執(zhí)行過(guò)程中,會(huì)動(dòng)態(tài)地將內(nèi)存劃分為如下幾大區(qū)域:
- 虛擬機(jī)棧
- 堆
- 方法區(qū)
- 程序計(jì)數(shù)器
- 本地方法棧
其中這些區(qū)域又可劃分為兩大類
-
由所有線程共享的區(qū)域
方法區(qū)胁塞、堆咏尝、本地方法棧
-
線程私有的數(shù)據(jù)區(qū)
虛擬機(jī)棧压语、程序計(jì)數(shù)器
程序計(jì)數(shù)器
- 是一塊較小的內(nèi)存
- 作用:當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器
- 通過(guò)改變這個(gè)計(jì)數(shù)器的值來(lái)選取下一條需要執(zhí)行的字節(jié)碼指令
- 為了線程切換后能恢復(fù)到正確的執(zhí)行位置,因此每條線程都需要一個(gè)獨(dú)立的程序計(jì)數(shù)器编检,各條線程之間的計(jì)數(shù)器互不影響胎食,獨(dú)立存儲(chǔ)
- 它的區(qū)域?yàn)椤熬€程私有”的內(nèi)存
- 此內(nèi)存區(qū)域是唯一一個(gè)在Java虛擬機(jī)規(guī)范中沒(méi)有規(guī)定任何OutOfMemoryError情況的區(qū)域
在Java的內(nèi)存分配中有這么一段話:
如果線程正在執(zhí)行的是一個(gè)Java方法,這個(gè)計(jì)數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址允懂;如果正在執(zhí)行的是Native方法厕怜,這個(gè)計(jì)數(shù)器值為空。
上述這句話引入了一個(gè)問(wèn)題:
我們知道蕾总,程序計(jì)數(shù)器用來(lái)存放字節(jié)碼指令的地址粥航;通過(guò)這個(gè)地址,虛擬機(jī)就能知道執(zhí)行到哪里谤专,以下下一步執(zhí)行什么躁锡,但是調(diào)用native
方法,值就變空了置侍,那么機(jī)器不就直接崩潰了嗎映之?
解釋:當(dāng)線程中調(diào)用native方法的時(shí)候,則重新啟動(dòng)一個(gè)新的線程蜡坊,那么新的線程的計(jì)數(shù)器為空則不會(huì)影響當(dāng)前線程的計(jì)數(shù)器杠输,相互獨(dú)立。而調(diào)用此方法的線程就會(huì)處于阻塞狀態(tài)秕衙,直到另外一個(gè)線程執(zhí)行結(jié)束才會(huì)恢復(fù)到運(yùn)行狀態(tài)
虛擬機(jī)棧
- 線程私有蠢甲,聲明周期與線程相同
- 方法在執(zhí)行時(shí)會(huì)創(chuàng)建一個(gè)棧幀,每一個(gè)方法被調(diào)用直至執(zhí)行完成的過(guò)程据忘,就對(duì)應(yīng)一個(gè)棧幀在虛擬機(jī)棧中從入棧到出棧的過(guò)程
上面提到了棧幀鹦牛,那么什么是棧幀呢
解釋:棧幀用來(lái)存儲(chǔ)局部變量表、操作棧勇吊、動(dòng)態(tài)鏈接曼追、方法出口等信息。
局部變量表
- 存放編譯期可知的各種基本類型數(shù)據(jù)(
boolean
汉规、byte
礼殊、char
、short
针史、int
晶伦、float
、long
啄枕、double
)婚陪、對(duì)象引用(reference類型)和returnAddress類型(指向了一條字節(jié)碼指令的地址) - 除了64位的
long
和double
類型的數(shù)據(jù)會(huì)占用2個(gè)局部變量空間,其余的數(shù)據(jù)類型只占用1個(gè) - 當(dāng)進(jìn)入一個(gè)方法频祝,這個(gè)方法需要在棧幀中分配多大局部變量空間時(shí)完全確定的近忙,方法在運(yùn)行期間不會(huì)改變局部變量表的大小
棧異常
- 線程請(qǐng)求的的棧深度大于虛擬機(jī)所允許的深度竭业,將拋出
StackOverflowError
異常 - 若虛擬機(jī)可動(dòng)態(tài)擴(kuò)展(當(dāng)前大部分的Java虛擬機(jī)都可動(dòng)態(tài)擴(kuò)展,只不過(guò)Java虛擬機(jī)規(guī)范中也允許固定長(zhǎng)度的虛擬機(jī)棧)及舍,但擴(kuò)展無(wú)法申請(qǐng)到足夠內(nèi)存時(shí)會(huì)拋出
StackOverflowError
異常
本地方法棧
- 與虛擬機(jī)棧的作用類似
- 區(qū)別:虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法服務(wù)未辆,而本地方法則為虛擬機(jī)使用到的Native方法服務(wù)
- Sun HotSpot虛擬機(jī)直接把本地方法棧和虛擬機(jī)棧合二為一
- 本地方法棧也會(huì)拋出
StackOverflowError
和OutOfMemoryError
異常
Java堆
- 是Java虛擬機(jī)所管理的內(nèi)存中最大的一塊
- 被所有線程共享
- 所有的對(duì)象實(shí)例以及數(shù)組都要在堆上分配
- 現(xiàn)在的收集器基本都采用分代收集算法,所以Java堆還可以細(xì)分為:新生代和老生代
- 如果堆中沒(méi)有內(nèi)存完成實(shí)例分配锯玛,并且堆也無(wú)法再擴(kuò)展時(shí)咐柜,將會(huì)拋出OutOfMemoryError異常
方法區(qū)
- 所有線程共享的內(nèi)存區(qū)域
- 存儲(chǔ)已被虛擬機(jī)加載的類信息、常量攘残、靜態(tài)變量
- 垃圾收集在這個(gè)區(qū)域比較少出現(xiàn)
- 這個(gè)區(qū)域的內(nèi)存回收目標(biāo)主要對(duì)常良池的回收和對(duì)類型的卸載
- 當(dāng)方法去無(wú)法滿足內(nèi)存分配需求時(shí)拙友,將拋出OutOfMemoryError異常
運(yùn)行時(shí)常量池
- 方法區(qū)的一部分
- Class文件中除了有類的版本、字段歼郭、方法遗契、接口等描述信息外,還有一項(xiàng)信息是常量池病曾,用于存放編譯器存放的各種字面量和符號(hào)引用牍蜂,這部分內(nèi)容將在類加載后存放到方法區(qū)的運(yùn)行時(shí)常量池中
- 除了保存Class文件中描述的符號(hào)引用外,還會(huì)把翻譯出來(lái)的直接引用也存儲(chǔ)在運(yùn)行時(shí)常量池中
對(duì)象訪問(wèn)
主流訪問(wèn)方式有兩種:句柄和直接指針
-
句柄
使用句柄詢問(wèn)方式泰涂,Java堆中將會(huì)劃分出一塊內(nèi)存來(lái)作為句柄池鲫竞,reference中存儲(chǔ)的就是對(duì)象的句柄地址。而句柄中包含了對(duì)象的實(shí)例數(shù)據(jù)和類型數(shù)據(jù)各自的具體地址信息逼蒙,如下圖所示:優(yōu)勢(shì):reference中存儲(chǔ)的是穩(wěn)定的句柄地址从绘,在對(duì)象被移動(dòng)時(shí)(垃圾收集時(shí)移動(dòng)對(duì)象是非常普遍的行為)只會(huì)改變句柄中的實(shí)例數(shù)據(jù)指針,而reference本身不需要被修改
-
直接指針
使用直接指針?lè)绞绞抢危琂ava堆對(duì)象的布局中就必須考慮如何放置訪問(wèn)類型數(shù)據(jù)的相關(guān)信息僵井,reference中直接存儲(chǔ)的就是對(duì)象地址,如圖所示:
優(yōu)勢(shì):速度更快驳棱,它節(jié)省了一次指針定位的時(shí)間開(kāi)銷(xiāo)驹沿,由于對(duì)象的訪問(wèn)在Java中非常頻繁,因此這類開(kāi)銷(xiāo)積少成多后也是一項(xiàng)非车负可觀的執(zhí)行成本,而目前我們使用的虛擬機(jī)Sun HotSpot就是使用第二種方式進(jìn)行對(duì)象訪問(wèn)的