JVM源碼分析之Java對(duì)象的內(nèi)存分配

簡書 占小狼
轉(zhuǎn)載請(qǐng)注明原創(chuàng)出處,謝謝!

看得越多,懂的越少,還年輕,多學(xué)習(xí)!

接著上篇《JVM源碼分析之Java對(duì)象的創(chuàng)建過程》,本文對(duì)Java對(duì)象的內(nèi)存分配過程進(jìn)行深入分析,其中有以下幾種分配方式:
1、從線程的局部緩沖區(qū)分配臨時(shí)內(nèi)存
2、從內(nèi)存堆中分配臨時(shí)內(nèi)存
3、從內(nèi)存堆中分配永久內(nèi)存

新建一個(gè)對(duì)象時(shí),由對(duì)應(yīng)的instanceKlass對(duì)象計(jì)算出需要多大的內(nèi)存,并調(diào)用CollectedHeapcommon_mem_allocate_noinit方法分配指定大小的內(nèi)存,實(shí)現(xiàn)如下:

從線程的局部緩沖區(qū)分配臨時(shí)內(nèi)存

TLAB技術(shù)是每個(gè)線程在Java堆中預(yù)先分配了一小塊內(nèi)存,當(dāng)有對(duì)象創(chuàng)建請(qǐng)求內(nèi)存分配時(shí),就會(huì)在該塊內(nèi)存上進(jìn)行分配,而不需要在Java堆通過同步控制進(jìn)行內(nèi)存分配。如果UseTLAB為真,則使用TLAB技術(shù)(Thread-Local Allocation Buffers),將分配工作交由線程自行完成,實(shí)現(xiàn)如下:

1、如果線程的局部緩沖區(qū)可以分配指定大小的內(nèi)存,則直接分配;
2、否則執(zhí)行allocate_from_tlab_slow在Java堆上進(jìn)行分配,實(shí)現(xiàn)如下:

3、通過allocate_new_tlab從Java堆上重新為線程分配一塊局部緩沖區(qū),實(shí)現(xiàn)如下:

其中mem_allocate方法實(shí)現(xiàn)從Java堆分配臨時(shí)內(nèi)存。

從內(nèi)存堆中分配臨時(shí)內(nèi)存

在內(nèi)存堆管理器看來,為普通對(duì)象分配內(nèi)存和為某一線程分配一塊本地分配緩沖區(qū)在本質(zhì)上都是一樣的,這塊內(nèi)存都是臨時(shí)的,只能從新生代或老年代中進(jìn)行分配,通過gc策略GenCollectorPolicy::mem_allocate_work方法進(jìn)行實(shí)現(xiàn),大概步驟如下:

step 1

1、gch->no_gc_in_progress()確保當(dāng)前JVM沒有正在進(jìn)行g(shù)c;
2、參數(shù)gc_overhead_limit_was_exceeded表示當(dāng)前內(nèi)存分配操作是否發(fā)生了gc,以及gc耗時(shí)是否超過設(shè)置限制,主要針對(duì)一些對(duì)延遲敏感的場景,當(dāng)該參數(shù)為true時(shí),拋出OOM的異常給上層;

step 2

通過重試機(jī)制確保內(nèi)存能夠分配成功:
1、首先在新生代采用無鎖的方式嘗試分配內(nèi)存,通過Atomic::cmpxchg_ptr的CAS操作對(duì)新生代空閑內(nèi)存進(jìn)行同步分配,最終實(shí)現(xiàn)如下:

2、如果分配失敗,則執(zhí)行step 3;

step 3

1、如果在新生代中內(nèi)存分配失敗,則通過加鎖方式進(jìn)行分配;
2、參數(shù)first_only表示當(dāng)前是否只應(yīng)該在新生代分配內(nèi)存,如果新生代的剩余空間不夠,則嘗試在老年代進(jìn)行分配;
3、依次嘗試從內(nèi)存各個(gè)代中分配內(nèi)存,實(shí)現(xiàn)如下:

4、如果內(nèi)存分配成功,則返回,否則執(zhí)行step 4;

step 4

1、gc_locker::is_active_and_needs_gc()為真時(shí),表示當(dāng)前其它線程已經(jīng)觸發(fā)了gc;
2、如果is_tlab為真,表示當(dāng)前線程正在為局部分配緩沖區(qū)申請(qǐng)內(nèi)存;
3、如果!gch->is_maximal_no_gc()為真,表示新生代或老年代可以進(jìn)行內(nèi)存擴(kuò)展,擴(kuò)展完成后,再次嘗試從各代中進(jìn)行分配,實(shí)現(xiàn)如下:

4、如果內(nèi)存擴(kuò)展之后還是沒有足夠的內(nèi)存滿足分配需求,則執(zhí)行step 5;

step 5

如果當(dāng)前線程沒有位于jni的臨界區(qū),將釋放Java堆的互斥鎖,以使得請(qǐng)求gc的線程可以進(jìn)行g(shù)c操作,等所有本地線程退出臨界區(qū)和gc完成后,將繼續(xù)循環(huán)嘗試分配內(nèi)存。

step 6

1、如果各代無法分配對(duì)象的內(nèi)存,說明需要觸發(fā)一次gc操作,提交VM一個(gè)GenCollectForAllocation操作,最終由名為VM Thread的JVM級(jí)線程調(diào)度執(zhí)行;
2、當(dāng)操作執(zhí)行成功并返回時(shí),如果gc鎖已被加鎖,說明已經(jīng)由其它線程觸發(fā)了gc,則繼續(xù)循環(huán)以等待gc完成;
3、否則當(dāng)前線程等待gc完成,判斷gc耗時(shí)是否超過設(shè)置的gc超時(shí)上限,并執(zhí)行軟引用的清除;
4、如果gc超時(shí),則給上層調(diào)用返回NULL,讓其拋出內(nèi)存溢出錯(cuò)誤;


我是占小狼
坐標(biāo)魔都,白天上班族,晚上是知識(shí)的分享者
如果讀完覺得有收獲的話,歡迎點(diǎn)贊加關(guān)注

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

相關(guān)閱讀更多精彩內(nèi)容

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