目錄
[toc]
一.運行時數(shù)據(jù)區(qū)域
Jvm在執(zhí)行程序的過程中會把它所管理的內(nèi)存劃分為若干個不同的區(qū)域;這些區(qū)域有各自的用途擦盾,以及創(chuàng)建和銷毀的時間嘲驾。
如下圖所示:
Image1.png
1.1 程序計數(shù)器(Program Counter Register)
線程私有淌哟,是一塊較小的內(nèi)存區(qū)域,可以看做是當前線程所執(zhí)行字節(jié)碼的行號指示器辽故。字節(jié)碼解釋器通過改變這個計數(shù)器的數(shù)值來選取下一條需要執(zhí)行的字節(jié)碼指令绞绒,分支、循環(huán)榕暇、跳轉(zhuǎn)蓬衡、異常處理都需要依賴它來完成。
注:如果線程正在執(zhí)行的是一個java方法彤枢,這個計數(shù)器記錄的是正在執(zhí)行的虛擬機字節(jié)碼指令的地址狰晚,如果是執(zhí)行本地(native)方法,這個計數(shù)器的值為空(undefined)缴啡。并且此內(nèi)存區(qū)域是唯一一個沒有oom異常的區(qū)域壁晒。
1.2 java虛擬機棧(Java Virtual Machine Stacks)
線程私有,虛擬機描述的是java方法執(zhí)行的內(nèi)存模型业栅,每個方法執(zhí)行時會創(chuàng)建一個棧幀(Stack Frame),用于存儲局部變量表秒咐,操作數(shù)棧,動態(tài)鏈接碘裕,方法出口信息等携取。局部變量表:存放了編譯期可知各種基本數(shù)據(jù)類型、對象的引用(reference類型)帮孔、returnAdress類型雷滋。注:局部變量表所需的內(nèi)存空間在編譯期間就完成分配,當進入一個方法時文兢,此方法需要在棧中分配多大的局部變量表的空間是完全確定的晤斩,在方法運行期間該變量表的大小不會改變。
此區(qū)域可能出現(xiàn)兩種異常:
- StackOverflowError異常姆坚,當線程請求的棧深度大于虛擬機所允許的深度導(dǎo)致澳泵。
- OutOfMemoryError異常,當虛擬機椉婧牵可以動態(tài)擴展兔辅,如果擴展時無法申請到足夠的內(nèi)存導(dǎo)致。
1.3 本地方法棧(Native Method Stack)
線程私有萍程,與虛擬機棧非常相似幢妄。區(qū)別在于虛擬機棧為java方法提供服務(wù),而本地方法棧是為Native方法提供服務(wù)茫负。
1.4 java堆(Java Heap)
線程共享蕉鸳,可能是jvm管理內(nèi)存區(qū)域最大的一塊,在虛擬機啟動時創(chuàng)建,存放對象實例潮尝,幾乎所有對象實例都在這里分配內(nèi)存榕吼,它是GC主要收集的區(qū)域。從內(nèi)存回收角度來看勉失,現(xiàn)代收集器都是使用分代收集算法羹蚣,所有它還可以細分為:Eden空間、From Survivor空間乱凿、To Survivor空間顽素。
注:可以通過(-Xmx和-Xms控制該區(qū)域大小)OutOfMemoryError異常:如果堆中沒有內(nèi)存可以完成實例分配徒蟆,將會導(dǎo)致該異常胁出。
1.5方法區(qū)(Method Area)
線程共享,用于存儲虛擬機加載的類的信息段审、常量全蝶、靜態(tài)變量、及時編譯器編譯后的代碼等數(shù)據(jù)寺枉。
- 運行時常量池(Run Time Pool):屬于方法區(qū)的一部分抑淫,用于存放編譯期生成的各種字面量和符號引用。
1.6直接內(nèi)存(Direct Memory)
不屬于虛擬機運行區(qū)域的一部分姥闪,但是這部分內(nèi)存也會被頻繁的使用始苇,也會出現(xiàn)OOM異常。在jdk1.4中甘畅,新加入的NIO埂蕊,引入了一種基于chanel(通道)和buffer(緩存區(qū))的I/O方式往弓,它可以使用Native函數(shù)直接分配堆外內(nèi)存疏唾,然后通過Java堆中的DirectByteBuffer對象作為這塊內(nèi)存的引用進行操作,避免了Java堆和Native堆中來回復(fù)制數(shù)據(jù),大大提升了性能函似。
二.Hotpot虛擬機對象探秘
2.1對象的創(chuàng)建
- 當遇到new指令時槐脏,首先去檢查這個指令的參數(shù)是否能在常量池中定位到一個類的符號的引用,并檢查這個類是否已經(jīng)被加載撇寞、解析顿天、初始化過。若沒有蔑担,需要先執(zhí)行類加載過程牌废。
- 為新生對象分配內(nèi)存;
- 將分配的內(nèi)存空間都初始化為零(不包括對象頭)啤握;
- 對對像進行必要的設(shè)置鸟缕,包括這個對象的類的實例信息。如何才能找到類的元數(shù)據(jù)信息、對象的哈希碼懂从、對象的Gc分代的年齡信息等授段;
- 上面過程完成后,虛擬機視角該對象已經(jīng)產(chǎn)生了番甩。但從java程序視角來看侵贵,對象創(chuàng)建才剛剛開始;
- 執(zhí)行<init>方法缘薛,對象創(chuàng)建完成窍育。
2.2對象的內(nèi)存布局
在hotpot虛擬機中,對象在內(nèi)存中的布局分為3塊區(qū)域:對象頭(Header)宴胧、實例數(shù)據(jù)(Instance Data)蔫骂、和對齊填充(Padding)。
- 對象頭: 包含兩部分信息
①Mark Word 存儲自身的運行時數(shù)據(jù)牺汤,包括哈希嗎辽旋、GC分代年齡、鎖狀態(tài)標志位檐迟、線程持有的鎖补胚、偏向線程ID、偏向時間戳等追迟。
②類型指針 對象執(zhí)行它的類的元數(shù)據(jù)的指針溶其,虛擬機通過這個指針來確定這個對象時那個類的實例。
-
實例數(shù)據(jù):
對象真正的存儲的有效信息敦间,也是在程序代碼中存在所定義的各種類型的字段內(nèi)容瓶逃,包括從父類繼承下來的。還是在子類中定義的廓块,都需要被記錄下來厢绝。
默認分配策略:對象的實例數(shù)據(jù)在hotsopt中,相同寬度的字段總是被分配到一起带猴。
-
對齊填充 :
不是必然存在的昔汉,也沒有特別的含義,只起占用符的作用拴清。存在的意義靶病,hotspot的自動內(nèi)存管理系統(tǒng)要求對象起始地址時8字節(jié)的整數(shù)倍,因此口予,若實例對象大小不是8的整數(shù)倍,就需要通過對齊填充來補全娄周。
2.3對象的訪問定位
主流的兩種對象訪問方式:
- ① 使用句柄池訪問:從java堆中劃分一塊內(nèi)存作為句柄池,reference中存儲的就是對象句柄的地址沪停。句柄中包含了對象的實例數(shù)據(jù)與類型數(shù)據(jù)各自的具體地址信息煤辨。也就是需要通過對象實例數(shù)據(jù)的指針找到堆中對象的實例數(shù)據(jù),通過對象的類型指針找到方法區(qū)中的對象的類型數(shù)據(jù)。
- ② 使用直接指針訪問:reference存儲的是直接的對象實例數(shù)據(jù)地址掷酗,也就是對象地址调违。省去了一次定位指針開銷,它的對象實例中包括了對象類型數(shù)據(jù)的指針泻轰,可以定位到方法區(qū)的對象類型數(shù)據(jù)技肩。
各自優(yōu)點:句柄池訪問最大的好處是reference中存儲的是穩(wěn)定的句柄地址,在對象被移動(gc過程中會移動對象的內(nèi)存地址)只會改變句柄中的實例數(shù)據(jù)指針浮声,而reference不用修改虚婿。直接指針訪問最大的優(yōu)點是訪問塊,它節(jié)省了一次指針定位的時間開銷泳挥,對象訪問很頻繁然痊,積少成多,是個很大的執(zhí)行成本屉符。
<u> TODO 補圖去理解</u>