簡書 占小狼
轉(zhuǎn)載請注明原創(chuàng)出處栽烂,謝謝!
看得越多焰手,懂的越少怀喉,還年輕躬拢,多學(xué)習见间!
接著上篇《JVM源碼分析之Java對象的創(chuàng)建過程》工猜,本文對Java對象的內(nèi)存分配過程進行深入分析篷帅,其中有以下幾種分配方式:
1、從線程的局部緩沖區(qū)分配臨時內(nèi)存
2惊橱、從內(nèi)存堆中分配臨時內(nèi)存
3李皇、從內(nèi)存堆中分配永久內(nèi)存
新建一個對象時宙枷,由對應(yīng)的instanceKlass
對象計算出需要多大的內(nèi)存,并調(diào)用CollectedHeap
的common_mem_allocate_noinit
方法分配指定大小的內(nèi)存卓囚,實現(xiàn)如下:
從線程的局部緩沖區(qū)分配臨時內(nèi)存
TLAB技術(shù)是每個線程在Java堆中預(yù)先分配了一小塊內(nèi)存哪亿,當有對象創(chuàng)建請求內(nèi)存分配時贤笆,就會在該塊內(nèi)存上進行分配芥永,而不需要在Java堆通過同步控制進行內(nèi)存分配。如果UseTLAB
為真板辽,則使用TLAB技術(shù)(Thread-Local Allocation Buffers)棘催,將分配工作交由線程自行完成醇坝,實現(xiàn)如下:
1、如果線程的局部緩沖區(qū)可以分配指定大小的內(nèi)存画畅,則直接分配夜赵;
2、否則執(zhí)行allocate_from_tlab_slow
在Java堆上進行分配摊腋,實現(xiàn)如下:
3兴蒸、通過allocate_new_tlab
從Java堆上重新為線程分配一塊局部緩沖區(qū)细办,實現(xiàn)如下:
其中mem_allocate
方法實現(xiàn)從Java堆分配臨時內(nèi)存。
從內(nèi)存堆中分配臨時內(nèi)存
在內(nèi)存堆管理器看來岛啸,為普通對象分配內(nèi)存和為某一線程分配一塊本地分配緩沖區(qū)在本質(zhì)上都是一樣的茴肥,這塊內(nèi)存都是臨時的,只能從新生代或老年代中進行分配瞬铸,通過gc策略GenCollectorPolicy::mem_allocate_work
方法進行實現(xiàn)嗓节,大概步驟如下:
step 1
1皆警、gch->no_gc_in_progress()
確保當前JVM沒有正在進行g(shù)c耀怜;
2、參數(shù)gc_overhead_limit_was_exceeded
表示當前內(nèi)存分配操作是否發(fā)生了gc,以及gc耗時是否超過設(shè)置限制从诲,主要針對一些對延遲敏感的場景,當該參數(shù)為true
時略步,拋出OOM的異常給上層定页;
step 2
通過重試機制確保內(nèi)存能夠分配成功:
1典徊、首先在新生代采用無鎖的方式嘗試分配內(nèi)存,通過Atomic::cmpxchg_ptr
的CAS操作對新生代空閑內(nèi)存進行同步分配羡铲,最終實現(xiàn)如下:
2也切、如果分配失敗腰湾,則執(zhí)行step 3费坊;
step 3
1、如果在新生代中內(nèi)存分配失敗导犹,則通過加鎖方式進行分配谎痢;
2卷雕、參數(shù)first_only
表示當前是否只應(yīng)該在新生代分配內(nèi)存,如果新生代的剩余空間不夠滨嘱,則嘗試在老年代進行分配太雨;
3魁蒜、依次嘗試從內(nèi)存各個代中分配內(nèi)存吩翻,實現(xiàn)如下:
4狭瞎、如果內(nèi)存分配成功搏予,則返回,否則執(zhí)行step 4碗殷;
step 4
1亿扁、gc_locker::is_active_and_needs_gc()
為真時鸟廓,表示當前其它線程已經(jīng)觸發(fā)了gc引谜;
2、如果is_tlab
為真毒涧,表示當前線程正在為局部分配緩沖區(qū)申請內(nèi)存贝室;
3、如果!gch->is_maximal_no_gc()
為真滑频,表示新生代或老年代可以進行內(nèi)存擴展峡迷,擴展完成后银伟,再次嘗試從各代中進行分配,實現(xiàn)如下:
4绘搞、如果內(nèi)存擴展之后還是沒有足夠的內(nèi)存滿足分配需求彤避,則執(zhí)行step 5;
step 5
如果當前線程沒有位于jni的臨界區(qū)夯辖,將釋放Java堆的互斥鎖琉预,以使得請求gc的線程可以進行g(shù)c操作,等所有本地線程退出臨界區(qū)和gc完成后蒿褂,將繼續(xù)循環(huán)嘗試分配內(nèi)存模孩。
step 6
1尖阔、如果各代無法分配對象的內(nèi)存榨咐,說明需要觸發(fā)一次gc操作,提交VM一個GenCollectForAllocation操作谴供,最終由名為VM Thread
的JVM級線程調(diào)度執(zhí)行块茁;
2、當操作執(zhí)行成功并返回時桂肌,如果gc鎖已被加鎖数焊,說明已經(jīng)由其它線程觸發(fā)了gc,則繼續(xù)循環(huán)以等待gc完成崎场;
3佩耳、否則當前線程等待gc完成,判斷gc耗時是否超過設(shè)置的gc超時上限谭跨,并執(zhí)行軟引用的清除干厚;
4、如果gc超時螃宙,則給上層調(diào)用返回NULL蛮瞄,讓其拋出內(nèi)存溢出錯誤;
我是占小狼
坐標魔都谆扎,白天上班族挂捅,晚上是知識的分享者
如果讀完覺得有收獲的話,歡迎點贊加關(guān)注