JVM內(nèi)存模型

github上的地址:DevelopBlog

概覽

java虛擬機(jī)(以下簡稱JVM)多種多樣,其中都必須遵循《java虛擬機(jī)規(guī)范》的要求,本篇文章只討論hotspot(SE7).
JVM在運(yùn)行程序時(shí),會把內(nèi)存劃分為幾個(gè)不同的區(qū)域,以方便線程的切換,GC,內(nèi)存的高效利用等,java運(yùn)行時(shí)數(shù)據(jù)區(qū)域示意圖如下:

運(yùn)行時(shí)數(shù)據(jù)區(qū)域示意圖.png

其中方法區(qū)為線程共享的區(qū)域

程序計(jì)數(shù)器

首先了解一下.class文件結(jié)構(gòu):
當(dāng)我們使用javac命令將.java文件編譯為.class文件之時(shí),編譯器將類的各項(xiàng)信息按照《java虛擬機(jī)規(guī)范》中的規(guī)范一次寫入.class文件,其中包括ava版本信息,包名,類信息,字段信息,方法信息等。其中方法塊內(nèi)容將被編譯成為可被JVM識別的一條條字節(jié)碼指令。
JVM執(zhí)行某一方法時(shí),加載.class文件之后,JVM的字節(jié)碼解釋器讀取文件中的操作指令(一條指令包含操作碼與操作數(shù)),讀取一條執(zhí)行一條,在多線程任務(wù)不斷切換中,如何才能記錄下各個(gè)線程的切換前的某一方法執(zhí)行的進(jìn)度呢?程序計(jì)數(shù)器就是為此而生,用來記錄正在執(zhí)行的JVM的字節(jié)碼的指令地址。由于它記錄的是某一條線程的執(zhí)行進(jìn)度,故他也是線程獨(dú)享的。

虛擬機(jī)棧

此部分即我們經(jīng)常提起的java堆棧中的。他的生性周期與線程相同,虛擬機(jī)中存儲是以棧幀為單位的,棧幀是虛擬機(jī)棧的的元素,在JVM執(zhí)行一新的方法時(shí),JVM會創(chuàng)建一個(gè)棧幀,該棧幀入棧,方法執(zhí)行結(jié)束后,該棧幀出。
棧幀用來存儲線程執(zhí)行過程時(shí)方法中的局部變量表,操作數(shù)棧,方法出口等其他信息。
局部變量表即是我們經(jīng)常討論的部分,他存儲了基礎(chǔ)類型(int,double,boolean等),對象引用(不是對象本身,而是一個(gè)對象的內(nèi)存地址)和returnAddress地址(前面提到的字節(jié)碼指令的地址)。
如圖所示:

線程,虛擬機(jī)棧,棧幀

本地方法棧

虛擬機(jī)棧類似,區(qū)別在于它用來在虛擬機(jī)執(zhí)行Native方法時(shí)使用

java開發(fā)中經(jīng)常提及的,JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)域中最大的一塊,它的唯一作用就是存放實(shí)例化后的對象以及數(shù)組。同時(shí)也是GC的主要區(qū)域。虛擬機(jī)棧中存儲了堆中對象的指針地址。
GC垃圾回收采用分代的思想來管理內(nèi)存,在內(nèi)存回收的角度來看,堆分為老年代新生代。
新生代是指新創(chuàng)建的對象,老年代是指存活時(shí)間比較長,經(jīng)過多輪GC任然存活的對象。

老年代,新生代

先介紹一下兩種GC類型:
新生代GC(Minor GC):指發(fā)生在新生代的垃圾回收,因?yàn)閖ava中大多數(shù)的java對象存活時(shí)間都不會很長,具備朝生夕滅的特點(diǎn),所以Minor GC的回收速度特別快,也特別頻繁。
老年代GC(Major GC/Full GC):指發(fā)生帶老年代的GC,大多數(shù)情況下會伴隨發(fā)生至少一次的新生代GC(Minor GC),由于使用不同的垃圾回收算法,故而回收速度非常慢。
(關(guān)于GC的算法以及常見垃圾回收器,將在另一篇文章中講解)

新生代是如何晉升為老年代呢?

參照上圖:
Survivor分為兩個(gè)區(qū)域,被稱為S0S1,兩個(gè)區(qū)域總有一個(gè)是空閑的。

步驟如下:

  • 新出生的對象優(yōu)先存活在Eden區(qū)域(Eden不夠時(shí),發(fā)起一次Minor GC),java在每個(gè)對象創(chuàng)建時(shí),為每個(gè)對象定義了一個(gè)年齡計(jì)數(shù)器,用于標(biāo)記一個(gè)對象的存活時(shí)間。

  • 第一次Minor GC新生代GC發(fā)生之后,如果該對象仍然存活,他的年齡+1,并將它從Eden移至Survivor中de S0區(qū)域(這時(shí)EdenS1區(qū)域?yàn)榭眨?/p>

*當(dāng)再次發(fā)生 Minor GC時(shí),此時(shí)回收的區(qū)域?yàn)?code>Eden和S0區(qū)域,此時(shí)S0區(qū)域沒有被回收的對象年齡+1。此時(shí)重點(diǎn)來了!S0對象有兩個(gè)去處,如果達(dá)到最大年齡(默認(rèn)15)的對象直接移動到老年代中,沒有到達(dá)年齡的對象的對象全部移至S1區(qū)域,S0清空;而Eden和上一步驟同樣,存活對象移至S1如下圖:

image.png

此次GC完成之后,S0Eden為空 如下圖

image.png

此后,

再次發(fā)生GC之后,EdenS1發(fā)生回收,將對象移至S0中,完成之后,EdenS1為空;

再次發(fā)生GC之后,EdenS0發(fā)生回收,將對象移至S1中,完成之后,EdenS0為空;

依此循環(huán),直至年齡達(dá)到一定程度(默認(rèn)15),對象進(jìn)入老年代。

年齡最大值默認(rèn)為15, 可以通過參數(shù) -XX MaxTenuringThreshold 設(shè)置

方法區(qū)

方法區(qū)
他存儲的是已經(jīng)被JVM加載的類信息,常量池,靜態(tài)變量,JVM即使編譯器等數(shù)據(jù),同樣也是線程共享區(qū)域。他經(jīng)常被看成是的邏輯部分,為了方便這部分內(nèi)存管理,JVM將垃圾回收范圍擴(kuò)展至方法區(qū),在GC中被稱為永久區(qū)(上圖中的Permanent),故而他經(jīng)常被稱為非堆,與java堆進(jìn)行區(qū)分。
GC對此部分的回收主要集中在對常量池的回收以及類型的卸載。

常量池

常量池是方法區(qū)的一部分,用于存放由javac編譯時(shí)生成的.class文件中的常量(例如:包名,類名,方法名,字段名,String字符串等,如下圖)。

class文件中字節(jié)碼常量類型

著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容