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