開篇閑話:
王侯將相皆有封地,大小根據(jù)爵位高低而不同,等級森嚴(yán)。在計(jì)算機(jī)世界里,大boss(操作系統(tǒng))很公平,給大家(進(jìn)程)都分配了同樣的內(nèi)存大?。m然這也是個假象)。我們的主角JVM出生的那天,大boss跟他說,你有4G的宅基地(內(nèi)存空間)可以使用,讓他好好想想怎么用,用的不好,年輕人,你將永無出頭之日?。?br> 小主JVM雖然年紀(jì)小,但是思考的深度不亞于他的爸爸 James Gosling。把自家的一畝三分地管理地不僅秩序井然,還年年碩果累累。
預(yù)備知識:認(rèn)識內(nèi)存
1、內(nèi)存是存放數(shù)據(jù)與指令的地方,cpu從緩存和內(nèi)存中獲取指令和數(shù)據(jù)執(zhí)行程序。
2、JVM的內(nèi)存區(qū)域是操作系統(tǒng)分配的一段內(nèi)存空間,不能拋開計(jì)算機(jī)內(nèi)存管理單看JVM的內(nèi)存管理。(了解操作系統(tǒng)、cpu、主存、I/O設(shè)備如何運(yùn)行與協(xié)作很重要,它能讓你總攬全局而非以偏概全)
3、JVM屏蔽了各操作系統(tǒng)的指令集的區(qū)別,程序的數(shù)據(jù)和指令加載到內(nèi)存中,通過JVM運(yùn)行,最終也是通過JVM將這些指令轉(zhuǎn)化為機(jī)器語言(比如匯編語言),由cpu中的運(yùn)算器和寄存器等進(jìn)行運(yùn)算。
1、計(jì)算機(jī)內(nèi)存模型
現(xiàn)在的計(jì)算機(jī)模型來自于馮·諾伊曼計(jì)算機(jī)結(jié)構(gòu),它解決了人類運(yùn)算思維的機(jī)器實(shí)現(xiàn)和延展,可進(jìn)行大量的復(fù)雜運(yùn)算,同時也存在諸多的問題(比如數(shù)據(jù)同步的問題等)。內(nèi)存在計(jì)算機(jī)中扮演的角色是指令和數(shù)據(jù)的存儲,與cpu合作完成程序運(yùn)行。假設(shè)需要計(jì)算1+1=?,內(nèi)存中的狀態(tài)可簡化為:

①內(nèi)存中存放程序指令MOVE[504]EAX(將地址為504處的內(nèi)存值復(fù)制到寄存器EAX中)、MOVE[505]EBX、ADD EAX EBX(將寄存器中EAX和EBX中的值相加放在EBX);
②內(nèi)存的每個存儲單元存儲的是指令還是值,由程序自己解析
2、內(nèi)存的運(yùn)行效率

類比廚師做菜,需要食材,食材可來源于超級市場、蔬果市場或者便利店,市場儲藏的位置遠(yuǎn)近決定了廚師做出菜肴的效率。類似的,程序運(yùn)行需要數(shù)據(jù),程序的數(shù)據(jù)在文件、輸入設(shè)備、內(nèi)存、緩存中,各個設(shè)備的工作原理的差別,導(dǎo)致運(yùn)行的效率區(qū)別天差地別:
cpu讀取數(shù)據(jù)優(yōu)先從緩存中讀?。ㄒ患?、二級、三級),如果沒有就到內(nèi)存中讀取。如果cpu的延遲時間是1s,則內(nèi)存的延遲為100-360s,速度很快。數(shù)據(jù)在緩存和內(nèi)存中均不存在,需要從硬盤中讀取,那就要等上1-12個月。內(nèi)存很重要的一個職責(zé)就是做為硬盤的緩沖區(qū),大大提升運(yùn)行效率。
JVM內(nèi)存模型及管理機(jī)制
1、運(yùn)行時數(shù)據(jù)區(qū)

①程序計(jì)數(shù)器:
程序計(jì)數(shù)器(program counter register)是一塊較小的存儲空間,它是每個線程私有的內(nèi)存區(qū)域,主要的作用是記住當(dāng)前程序執(zhí)行到哪條指令,以便在線程切換后可以準(zhǔn)確的繼續(xù)執(zhí)行指令。
怎么記錄呢,記錄什么呢?其實(shí)很簡單,就是記錄指令的內(nèi)存地址。

②java虛擬機(jī)棧
虛擬機(jī)棧屬于線程私有,與線程同生共死。每當(dāng)調(diào)用一個方法時,即在虛擬機(jī)棧中創(chuàng)建一個函數(shù)棧幀:

局部變量表:
1、存放編譯期可知的各種基本類型(boolean,byte,char,short,int,long,float,double)、對象引用類型(指向?qū)ο蟮刂菲鹗嘉恢玫闹羔樆虼韺ο蟮木浔?br> 2、存儲的單元稱為slot,大小為32位;
3、數(shù)組結(jié)構(gòu),通過索引訪問;
4、局部變量表的空間大?。╯lot數(shù)量)在編譯期便確定下來。
操作數(shù)棧:
1、被稱為“基于棧的執(zhí)行引擎”
2、操作數(shù)棧是執(zhí)行字節(jié)碼指令時,進(jìn)行運(yùn)算的單元;
3、數(shù)組結(jié)構(gòu),通過棧操作(壓棧和出棧)來訪問,下面的代碼展示了0與1相加時操作數(shù)棧運(yùn)行的字節(jié)碼指令:
iload_0 // 將int類型的數(shù)字0壓入棧中
iload_1 // 將int類型的1壓入棧中
iadd // 將剛才壓入的兩個數(shù)字pop出去,相加,壓入棧中
istore_2 // 將值pop到局部變量表中slot為2的地方
③方法區(qū):
各個線程共享的內(nèi)存區(qū)域,用于存儲已經(jīng)被加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)。

④方法區(qū)中的運(yùn)行時常量池
java的Class文件中包含類的版本、字段、方法、接口等的描述信息,還有一項(xiàng):常量池,用于存放編譯期生成的字面量和符號引用。這部分內(nèi)容將在類加載到方法區(qū)后,存放在方法區(qū)的運(yùn)行時常量池。
運(yùn)行時常量池的存在是因?yàn)槌A砍氐膬?nèi)容是可以動態(tài)改變的,不僅僅是編譯期確定的常量池內(nèi)容。
⑤對象的內(nèi)存布局
1、對象可不是我們平常畫一個圓圈,標(biāo)注“對象”兩個字那么簡單。它是用來存儲運(yùn)行時數(shù)據(jù)的地方;
2、對象分配的內(nèi)存空間中主要包含三個部分:對象頭、實(shí)例數(shù)據(jù)和對齊填充(包含的信息如圖所示);
3、需要特別說明的是“實(shí)例數(shù)據(jù)”,這部分存儲的是有效信息,就是在類代碼中定義的字段內(nèi)容(包含成員變量和局部變量,這是我個人的理解,待探討),包含繼承自父類的字段。

⑥如何找到對象
我們在函數(shù)棧幀中會用reference代表對象的類型,通過refrence來定位對象。在jvm規(guī)范中并沒有明確定義reference,現(xiàn)在主流的實(shí)現(xiàn)方式有兩種:
1、指針:reference指向堆中對象,指針指向方法區(qū)的對象類型數(shù)據(jù);
2、句柄:堆中劃分一片內(nèi)存做為句柄池,reference存儲的是對象的句柄地址,句柄中包含對象實(shí)例數(shù)據(jù)和類型數(shù)據(jù)的地址


最后,上張圖,縱覽一下全局:
