JVM源碼分析之YGC的來龍去脈

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

換了新工作阶捆,確實(shí)比以前忙多了凌节,從而也擱置了自己興趣,不過還是想方設(shè)法的擠出一點(diǎn)時(shí)間把YGC的一些細(xì)節(jié)實(shí)現(xiàn)重新看了幾遍洒试,HotSpot里的不少代碼寫的太糾結(jié)倍奢,山路十八彎,要理清楚確實(shí)需要費(fèi)點(diǎn)時(shí)間垒棋。

YGC是JVM GC當(dāng)前最為頻繁的一種GC卒煞,一個(gè)高并發(fā)的服務(wù)在運(yùn)行期間,會進(jìn)行大量的YGC叼架,發(fā)生YGC時(shí)畔裕,會進(jìn)行STW,一般時(shí)間都很短乖订,除非碰到Y(jié)GC時(shí)扮饶,存在大量的存活對象需要進(jìn)行拷貝。

一次YGC過程主要分成兩個(gè)步驟:
1乍构、查找GC Roots贴届,拷貝所引用的對象到 to 區(qū);
2蜡吧、遞歸遍歷步驟1中對象毫蚓,并拷貝其所引用的對象到 to 區(qū),當(dāng)然可能會存在自然晉升昔善,或者因?yàn)?to 區(qū)空間不足引起的提前晉升的情況元潘;

下面進(jìn)行分析的是Serial GC,ParNew GC可以理解成并發(fā)的Serial GC君仆,實(shí)現(xiàn)原理都差不多翩概,看源碼的話建議看Serial GC 的實(shí)現(xiàn)類DefNewGeneration牲距,畢竟單線程實(shí)現(xiàn)的復(fù)雜性會低一點(diǎn),在DefNewGeneration中钥庇,會看到一些以 *-Closure 方式命名的類牍鞠,這些都是封裝起來的回調(diào)函數(shù),是為了讓GC的具體邏輯與對象內(nèi)部的字段遍歷邏輯能夠松耦合评姨,比如ScanClosure 與 FastScanClosure 作為回調(diào)函數(shù)傳入到各個(gè)方法中难述,實(shí)現(xiàn)GC實(shí)現(xiàn)的對象遍歷,正因?yàn)檫@種實(shí)現(xiàn)方式吐句,大大增加了閱讀源碼的難度胁后。

查找GC Roots

YGC的第一步根據(jù)GC Roots找出第一批活躍的對象,Hotspot中通過gch->gen_process_strong_roots方法實(shí)現(xiàn)

在黃色框的實(shí)現(xiàn)中嗦枢,SharedHeap::process_strong_roots()掃描了所有一定是GC Roots的內(nèi)存區(qū)域攀芯,有興趣的可以查看process_strong_roots的實(shí)現(xiàn),主要包括了以下東西:

  • Universe類中所引用的一些必須存活的對象 Universe::oops_do(roots)
  • 所有JNI Handles JNIHandles::oops_do(roots)
  • 所有線程的棧 Threads::oops_do(roots, code_roots)
  • 所有被Synchronize鎖持有的對象 ObjectSynchronizer::oops_do(roots)
  • VM內(nèi)實(shí)現(xiàn)的MBean所持有的對象 Management::oops_do(roots)
  • JVMTI所持有的對象 JvmtiExport::oops_do(roots)
  • (可選)所有已加載的類 或 所有已加載的系統(tǒng)類 SystemDictionary::oops_do(roots)
  • (可選)所有駐留字符串(StringTable) StringTable::oops_do(roots)
  • (可選)代碼緩存(CodeCache) CodeCache::scavenge_root_nmethods_do(code_roots)
  • (可選)PermGen的remember set所記錄的存在跨代引用的區(qū)域 rem_set()->younger_refs_iterate(perm_gen(), perm_blk)

YGC在執(zhí)行時(shí)只收集young generation文虏,不收集old generation和perm generation侣诺,并不會做類的卸載行為,所以上述可選部分都作為Strong root氧秘,但是在FGC時(shí)就不會當(dāng)作Strong root了年鸳。

紅色框中的實(shí)現(xiàn)邏輯對于YGC來說是沒有意義的,因?yàn)閘evel=0敏储,Hotspot中唯一用到這個(gè)地方的只有CMS GC實(shí)現(xiàn),默認(rèn)只收集old generation朋鞍,所以需要掃描young generation作為它的Strong root已添。

講到這里,似乎有一部分被忽略了滥酥,如果一個(gè)old generation的對象引用了young generation更舞,那么這個(gè)old generation的對象肯定也屬于Strong root的一部分,這部分邏輯并沒有在process_strong_roots中實(shí)現(xiàn)坎吻,而是在綠色框中實(shí)現(xiàn)了缆蝉,其中rem_set中保存了old generation中dirty card的對應(yīng)區(qū)域,每次對象的拷貝移動都會檢查一下是否產(chǎn)生了新的跨代引用瘦真,比如有對象晉升到了old generation刊头,而該對象還引用了young generation的對象,這種情況下會把相應(yīng)的card置為dirty诸尽,下次YGC的時(shí)候只會掃描dirty card所指內(nèi)存的對象原杂,避免掃描所有的old generation對象。

遍歷活躍對象

在查找GC Roots的步驟中您机,已經(jīng)找出了第一批存活的對象穿肄,這些存活對象可能在 to-space年局,也有可能直接晉升到了 old generation,這些區(qū)域都是需要進(jìn)行遍歷的咸产,保證所有的活躍對象都能存活下來矢否。

遍歷過程的實(shí)現(xiàn)由FastEvacuateFollowersClosure類的do_void方法完成,這又是一個(gè)*-Closure 方式命名的類脑溢,實(shí)現(xiàn)如下

每個(gè)內(nèi)存區(qū)域都有兩個(gè)指針變量僵朗,分別是 _saved_mark_word 和 _top,其中_saved_mark_word 指向當(dāng)前遍歷對象的位置焚志,_top指向當(dāng)前內(nèi)存區(qū)域可分配的位置衣迷,其中_saved_mark_word 到 _top之間的對象是已拷貝,但未掃描的對象酱酬。


GC Roots引用的對象拷貝完成后壶谒,to-space的_saved_mark_word和_top的狀態(tài)如上圖所示,假設(shè)期間沒有對象晉升到old generation膳沽。每次掃描一個(gè)對象汗菜,_saved_mark_word會往前移動,期間也有新的對象會被拷貝到to-space挑社,_top也會往前移動陨界,直到_saved_mark_word追上_top,說明to-space的對象都已經(jīng)遍歷完成痛阻。

其中while循環(huán)條件 while (!_gch->no_allocs_since_save_marks(_level)菌瘪,就是在判斷各個(gè)內(nèi)存代中的_saved_mark_word是否已經(jīng)追到_top,如果還沒有追上阱当,就執(zhí)行_gch->oop_since_save_marks_iterate進(jìn)行遍歷俏扩,實(shí)現(xiàn)如下:

從代碼實(shí)現(xiàn)可以看出對新生代、老年代和永久代都會進(jìn)行遍歷弊添,其中新生代的遍歷實(shí)現(xiàn)如下:

這里會對eden录淡、from和to分別進(jìn)行遍歷,第一次看這塊邏輯的時(shí)候很納悶油坝,為什么要對eden和from-space進(jìn)行遍歷嫉戚,from倒沒什么問題,_saved_mark_word和_top一般都是相同的澈圈,但是eden區(qū)的_saved_mark_word明顯不會等于_top彬檀,一直沒有找到在eden區(qū)分配對象時(shí),改變_top的同時(shí)也改變_saved_mark_word的邏輯瞬女,后來發(fā)現(xiàn)GenCollectedHeap::do_collection方法中凤覆,在調(diào)用各個(gè)代的collect之前,會調(diào)用save_marks()方法拆魏,將_saved_mark_word設(shè)置為_top盯桦,這樣在發(fā)生YGC時(shí)慈俯,eden區(qū)的對象其實(shí)是不會被遍歷的,被這個(gè)疑惑困擾了好久拥峦,結(jié)果是個(gè)遺留代碼贴膘。

to-space對象的遍歷實(shí)現(xiàn):


這里的blk變量是傳遞過來的FastScanClosure回調(diào)函數(shù),oop_iterate方法會遍歷該對象的所有引用略号,并調(diào)用回調(diào)函數(shù)的do_oop_work方法處理這里引用所指向的對象刑峡。

do_oop_work的實(shí)現(xiàn)

在FastScanClosure回調(diào)函數(shù)的do_oop_work方法實(shí)現(xiàn)中,紅框的是重要的部分玄柠,因?yàn)榭赡艽嬖诙鄠€(gè)對象共同引用一個(gè)對象突梦,所以在遍歷過程中,可能會遇到已經(jīng)處理過的對象羽利,如果遇到這樣的對象宫患,就不會再次進(jìn)行復(fù)制了,如果該對象沒有被拷貝過这弧,則調(diào)用 copy_to_survivor_space 方法拷貝對象到to-space或者晉升到old generation娃闲,這里提一下ParNew的實(shí)現(xiàn),因?yàn)槭遣l(fā)執(zhí)行的匾浪,所以可能存在多個(gè)線程拷貝了同一個(gè)對象到to-space皇帮,不過通過原子操作,保證了只有一個(gè)對象是有效的蛋辈。

copy_to_survivor_space 的實(shí)現(xiàn):

拷貝對象的目標(biāo)空間不一定是to-space属拾,也有可能是old generation,如果一個(gè)對象經(jīng)歷了很多次YGC冷溶,會從young generation直接晉升到old generation渐白,為了記錄對象經(jīng)歷的YGC次數(shù),在對象頭的mark word 數(shù)據(jù)結(jié)構(gòu)中有一個(gè)位置記錄著對象的YGC次數(shù)挂洛,也叫對象的年齡礼预,如果掃描到的對象眠砾,其年齡小于某個(gè)閾值(tenuring threshold)虏劲,該對象會被拷貝到to-space,并增加該對象的年齡褒颈,同時(shí)to-space的_top指針也會往后移動柒巫,這個(gè)新對象等待著被掃描。


個(gè)人公眾號


如果該對象的年齡大于某個(gè)閾值谷丸,會晉升到old generation堡掏,或者在拷貝到to-space時(shí)空間不足,也會提前晉升到old generation刨疼,晉升過程通過老年代_next_gen的promote方法實(shí)現(xiàn)泉唁,如果old generation也沒有足夠的空間容納該對象鹅龄,則會觸發(fā)晉升失敗。

參考
http://hllvm.group.iteye.com/group/topic/39376

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末亭畜,一起剝皮案震驚了整個(gè)濱河市扮休,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌拴鸵,老刑警劉巖玷坠,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異劲藐,居然都是意外死亡八堡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門聘芜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來兄渺,“玉大人,你說我怎么就攤上這事厉膀∪茉牛” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵服鹅,是天一觀的道長凳兵。 經(jīng)常有香客問我,道長企软,這世上最難降的妖魔是什么庐扫? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮仗哨,結(jié)果婚禮上形庭,老公的妹妹穿的比我還像新娘。我一直安慰自己厌漂,他們只是感情好萨醒,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著苇倡,像睡著了一般富纸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上旨椒,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天晓褪,我揣著相機(jī)與錄音,去河邊找鬼综慎。 笑死涣仿,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播好港,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼愉镰,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了钧汹?” 一聲冷哼從身側(cè)響起岛杀,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎崭孤,沒想到半個(gè)月后类嗤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡辨宠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年遗锣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嗤形。...
    茶點(diǎn)故事閱讀 40,096評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡精偿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出赋兵,到底是詐尸還是另有隱情笔咽,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布霹期,位于F島的核電站叶组,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏历造。R本人自食惡果不足惜甩十,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吭产。 院中可真熱鬧侣监,春花似錦、人聲如沸臣淤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽邑蒋。三九已至姓蜂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間寺董,已是汗流浹背覆糟。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工刻剥, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留遮咖,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓造虏,卻偏偏與公主長得像御吞,于是被迫代替她去往敵國和親麦箍。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容