JVM完全解讀之YGC來(lái)龍去脈分析
換了新工作,確實(shí)比以前忙多了,從而也擱置了自己興趣,不過(guò)還是想方設(shè)法的擠出一點(diǎn)時(shí)間把YGC的一些細(xì)節(jié)實(shí)現(xiàn)重新看了幾遍,HotSpot里的不少代碼寫的太糾結(jié),山路十八彎,要理清楚確實(shí)需要費(fèi)點(diǎn)時(shí)間。
一次YGC過(guò)程主要分成兩個(gè)步驟:
1、查找GC Roots,拷貝所引用的對(duì)象到 to 區(qū);
2、遞歸遍歷步驟1中對(duì)象,并拷貝其所引用的對(duì)象到 to 區(qū),當(dāng)然可能會(huì)存在自然晉升,或者因?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ù)雜性會(huì)低一點(diǎn),在DefNewGeneration中,會(huì)看到一些以 *-Closure 方式命名的類,這些都是封裝起來(lái)的回調(diào)函數(shù),是為了讓GC的具體邏輯與對(duì)象內(nèi)部的字段遍歷邏輯能夠松耦合,比如ScanClosure 與 FastScanClosure 作為回調(diào)函數(shù)傳入到各個(gè)方法中,實(shí)現(xiàn)GC實(shí)現(xiàn)的對(duì)象遍歷,正因?yàn)檫@種實(shí)現(xiàn)方式,大大增加了閱讀源碼的難度。
查找GC Roots
YGC的第一步根據(jù)GC Roots找出第一批活躍的對(duì)象,Hotspot中通過(guò)gch->gen_process_strong_roots方法實(shí)現(xiàn)
在黃色框的實(shí)現(xiàn)中,SharedHeap::process_strong_roots()
YGC在執(zhí)行時(shí)只收集young generation,不收集old generation和perm generation,并不會(huì)做類的卸載行為,所以上述可選部分都作為Strong root,但是在FGC時(shí)就不會(huì)當(dāng)作Strong root了。
紅色框中的實(shí)現(xiàn)邏輯對(duì)于YGC來(lái)說(shuō)是沒(méi)有意義的,因?yàn)閘evel=0,Hotspot中唯一用到這個(gè)地方的只有CMS GC實(shí)現(xiàn),默認(rèn)只收集old generation,所以需要掃描young generation作為它的Strong root。
講到這里,似乎有一部分被忽略了,如果一個(gè)old generation的對(duì)象引用了young generation,那么這個(gè)old generation的對(duì)象肯定也屬于Strong root的一部分,這部分邏輯并沒(méi)有在process_strong_roots實(shí)現(xiàn),而是在綠色框中實(shí)現(xiàn)了,其中rem_set中保存了old generation中dirty card的對(duì)應(yīng)區(qū)域,每次對(duì)象的拷貝移動(dòng)都會(huì)檢查一下是否產(chǎn)生了新的跨代引用,比如有對(duì)象晉升到了old generation,而該對(duì)象還引用了young generation的對(duì)象,這種情況下會(huì)把相應(yīng)的card置為dirty,下次YGC的時(shí)候只會(huì)掃描dirty card所指內(nèi)存的對(duì)象,避免掃描所有的old generation對(duì)象。
遍歷活躍對(duì)象
在查找GC Roots的步驟中,已經(jīng)找出了第一批存活的對(duì)象,這些存活對(duì)象可能在 to-space,也有可能直接晉升到了 old generation,這些區(qū)域都是需要進(jìn)行遍歷的,保證所有的活躍對(duì)象都能存活下來(lái)。
遍歷過(guò)程的實(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)前遍歷對(duì)象的位置,_top指向當(dāng)前內(nèi)存區(qū)域可分配的位置,其中_saved_mark_word 到 _top之間的對(duì)象是已拷貝,但未掃描的對(duì)象。
GC Roots引用的對(duì)象拷貝完成后,to-space的_saved_mark_word和_top的狀態(tài)如上圖所示,假設(shè)期間沒(méi)有對(duì)象晉升到old generation。每次掃描一個(gè)對(duì)象,_saved_mark_word會(huì)往前移動(dòng),期間也有新的對(duì)象會(huì)被拷貝到to-space,_top也會(huì)往前移動(dòng),直到_saved_mark_word追上_top,說(shuō)明to-space的對(duì)象都已經(jīng)遍歷完成。
其中while循環(huán)條件while (!_gch->no_allocs_since_save_marks(_level),就是在判斷各個(gè)內(nèi)存代中的_saved_mark_word是否已經(jīng)追到_top,如果還沒(méi)有追上,就執(zhí)行_gch->oop_since_save_marks_iterate進(jìn)行遍歷,實(shí)現(xiàn)如下:
從代碼實(shí)現(xiàn)可以看出對(duì)新生代、老年代和永久代都會(huì)進(jìn)行遍歷,其中新生代的遍歷實(shí)現(xiàn)如下:
這里會(huì)對(duì)eden、from和to分別進(jìn)行遍歷,第一次看這塊邏輯的時(shí)候很納悶,為什么要對(duì)eden和from-space進(jìn)行遍歷,from倒沒(méi)什么問(wèn)題,_saved_mark_word和_top一般都是相同的,但是eden區(qū)的_saved_mark_word明顯不會(huì)等于_top,一直沒(méi)有找到在eden區(qū)分配對(duì)象時(shí),改變_top的同時(shí)也改變_saved_mark_word的邏輯,后來(lái)發(fā)現(xiàn)GenCollectedHeap::do_collection
方法中,在調(diào)用各個(gè)代的collect之前,會(huì)調(diào)用save_marks()方法,將_saved_mark_word設(shè)置為_(kāi)top,這樣在發(fā)生YGC時(shí),eden區(qū)的對(duì)象其實(shí)是不會(huì)被遍歷的,被這個(gè)疑惑困擾了好久,結(jié)果是個(gè)遺留代碼。
to-space對(duì)象的遍歷實(shí)現(xiàn):
這里的blk變量是傳遞過(guò)來(lái)的FastScanClosure回調(diào)函數(shù),oop_iterate方法會(huì)遍歷該對(duì)象的所有引用,并調(diào)用回調(diào)函數(shù)的do_oop_work方法處理這里引用所指向的對(duì)象。
do_oop_work
的實(shí)現(xiàn)
在FastScanClosure回調(diào)函數(shù)的do_oop_work方法實(shí)現(xiàn)中,紅框的是重要的部分,因?yàn)榭赡艽嬖诙鄠€(gè)對(duì)象共同引用一個(gè)對(duì)象,所以在遍歷過(guò)程中,可能會(huì)遇到已經(jīng)處理過(guò)的對(duì)象,如果遇到這樣的對(duì)象,就不會(huì)再次進(jìn)行復(fù)制了,如果該對(duì)象沒(méi)有被拷貝過(guò),則調(diào)用 copy_to_survivor_space
方法拷貝對(duì)象到to-space或者晉升到old generation,這里提一下ParNew的實(shí)現(xiàn),因?yàn)槭遣l(fā)執(zhí)行的,所以可能存在多個(gè)線程拷貝了同一個(gè)對(duì)象到to-space,不過(guò)通過(guò)原子操作,保證了只有一個(gè)對(duì)象是有效的。
copy_to_survivor_space
的實(shí)現(xiàn):
拷貝對(duì)象的目標(biāo)空間不一定是to-space,也有可能是old generation,如果一個(gè)對(duì)象經(jīng)歷了很多次YGC,會(huì)從young generation直接晉升到old generation,為了記錄對(duì)象經(jīng)歷的YGC次數(shù),在對(duì)象頭的mark word 數(shù)據(jù)結(jié)構(gòu)中有一個(gè)位置記錄著對(duì)象的YGC次數(shù),也叫對(duì)象的年齡,如果掃描到的對(duì)象,其年齡小于某個(gè)閾值(tenuring threshold),該對(duì)象會(huì)被拷貝到to-space,并增加該對(duì)象的年齡,同時(shí)to-space的_top指針也會(huì)往后移動(dòng),這個(gè)新對(duì)象等待著被掃描。
如果該對(duì)象的年齡大于某個(gè)閾值,會(huì)晉升到old generation,或者在拷貝到to-space時(shí)空間不足,也會(huì)提前晉升到old generation,晉升過(guò)程通過(guò)老年代_next_gen的promote方法實(shí)現(xiàn),如果old generation也沒(méi)有足夠的空間容納該對(duì)象,則會(huì)觸發(fā)晉升失敗。
以上就是JVM完全解讀之YGC來(lái)龍去脈分析的詳細(xì)內(nèi)容,更多關(guān)于JVM解讀YGC分析的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
mybatis-generator-gui 工具使用(圖形化工具)
基于 mybatis generator 開(kāi)發(fā)一款界面工具, 本工具可以使你非常容易及快速生成 Mybatis 的 Java POJO 文件及數(shù)據(jù)庫(kù) Mapping 文件。本文重點(diǎn)給大家介紹mybatis-generator-gui 工具使用,感興趣的朋友一起看看吧2022-03-03mybatis-plus動(dòng)態(tài)表名的實(shí)現(xiàn)示例
這篇文章主要介紹了mybatis-plus動(dòng)態(tài)表名的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04解決springboot項(xiàng)目找不到resources目錄下的資源問(wèn)題
這篇文章主要介紹了解決springboot項(xiàng)目找不到resources目錄下的資源問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08MyBatis typeAliases元素標(biāo)簽(含注解方式)及其屬性、設(shè)置方式
這篇文章主要介紹了MyBatis typeAliases元素標(biāo)簽(含注解方式)及其屬性、設(shè)置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09SpringBoot父子線程數(shù)據(jù)傳遞的五種方案介紹
在實(shí)際開(kāi)發(fā)過(guò)程中我們需要父子之間傳遞一些數(shù)據(jù),比如用戶信息等。該文章從5種解決方案解決父子之間數(shù)據(jù)傳遞困擾,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2022-09-09