向前迈进!走入GC世界:G1 GC原理深入解析
第零章:名詞解釋
mutator:應用線程
STW:Stop-The-World,指除了GC線程,其它所有線程全部暫停的一段時間
并發:指代GC線程與mutator在同一時刻執行任務
并行:指代多個GC線程在同一時刻執行任務
Young GC:新生代回收
Mixed GC:混合回收
Full GC:整堆回收
第一章:G1基本概念
這里介紹的主要是一些老生常談的內容,比較熟悉的同學可以直接跳過本部分內容G1的目標
G1作為一個增量垃圾回收器,致力于保證高吞吐量與軟實時性的最佳平衡,其應用場景最好需要包含以下特性(滿足這些特性的話,則可能更適合G1出馬,否則可能其他GC更合適):
- 堆內存大小超過10G,且存活對象占用比例超過50%
- 對象分配和晉升速率可能隨時間有顯著變化
- 堆中存在大量碎片
- 預測的最大停頓時間不超過幾百毫秒
對于其它垃圾回收器來說,由于整堆回收的特性和缺乏啟發式調整的機制,導致其無法很好的應付上述場景,而G1由于開創性的停頓預測模型與獨特的堆結構,則恰恰相反。
增量GC:通過慢慢GC來縮短mutator最大暫停時間的一種手段G1的堆結構
?G1 GC 堆分布圖
G1中,將堆內存劃分為了大小相等的內存塊,稱之為Heap Region,Heap Region是內存分配和內存回收的最小單位,由于全稱過長,后文中可能使用分區、區域或HR代指Heap Region,望知悉。
圖中紅色區域指代的是新生代(young generation),藍色區域指代的是老年代(old generation),標注為S的紅色區域的為Suvivor Region,標注為H的藍色區域為Humoungous Region。
從上圖中可以看到,在G1收集器中,對象占用的內存和代際的分布都不是連續的,而且對于灰色區域來說,它隨時可以為了實現軟實時性,成為任何確定的代際區域,這是G1和其他收集器在堆內存結構上的最大差別,也是為什么G1更適合處理「堆中存在大量碎片」場景的原因。
新生代&老年代:通常垃圾收集器會根據對象的特性來選擇不同的算法進行垃圾回收,對象會依據特性存儲在不同的內存區域中,使用不同的回收算法。新生代&老年代便是依據對象生命周期所劃分的內存區域。停頓預測模型
在G1 GC中,用戶通過-XX:MaxGCPauseMills 來設定期望最大停頓時間,而具體則是由G1 GC中的停頓預測模型實現。
卡表
JVM將堆內存劃分為了2次冪大小的卡頁(Card Page),使用卡表來記錄其內存狀態。默認情況下,卡頁的大小為512B。
卡表(Card Table)是由1B組成的數組,卡表里的元素稱為卡片(Card),每個卡片對應堆內存中的一個卡頁,卡片數組位置為堆內存地址除卡頁大小(向下取整)。
?卡表與卡頁映射關系圖
堆中的對象所對應的卡片在卡表中的索引值可以通過以下公式快速計算出來。
(對象的地址-堆的頭部地址)/512卡表是一個全局表,其核心是利用了分桶思想,犧牲了一定的索引效率,不過使得JVM僅使用少量內存上即能快速描述內存狀態。
記憶集(RSet)
記憶集與卡表密切相關,上一節中介紹了卡表的結構和職責,但是仍不知其具體應用場景有哪些
,接下來我便來介紹其最為重要的一個應用場景:記憶集。
記憶集英文全稱為RemerberSet,簡稱RSet,是一種抽象概念,其核心思想是借由卡表,記錄對象在不同代際間的引用關系,加速垃圾回收的速度。
JVM會通過可達性分析算法標記存活對象來幫助進行垃圾回收,不過在分代GC中,新生代和老年代處于不同的回收階段,如果我僅僅只需要需要回收新生代,卻標記了老年代的對象,那么這無疑是不必要的,所以JVM設計了RSet這樣的玩意來避免這種現象,使其即便不掃描全部對象,也可以查到待回收對象所在分區被其它分區引用的情況。
記憶集的結構及使用示例
假設有兩個分區(Heap Region),其區域內對象的引用關系如下圖所示:
每個HR中都會額外開辟一塊存儲空間,用于存儲記憶集,記憶集的結構為一個Hash表,Key為引用本分區的其它分區的地址,Value是卡片(Card)在卡表的索引值,即分區中的哪些卡頁引用了本分區。
拿上圖兩個分區的引用關系舉例,對象b引用了對象a,則在HR A中的記憶集,會存儲對象b所在卡頁對本分區的引用。在這個圖中,對象b所在區域為HR B,所在卡頁對應卡表索引2048的位置,則HR A的記憶集中會新增一個「B-2048」的引用關系
?記憶集結構圖
記憶集如何篩選記錄引用關系
上邊介紹了記憶集的結構及其如何記錄引用關系的示例,但是堆對象引用那么多,如果全部一一記錄的話,我們的內存就爆炸了,所以我們需要明確哪些引用關系我們需要記錄,哪些我們不需要記錄。
在G1中提供了3種收集算法。新生代回收、混合回收和FullGC。新生代回收總是收集所有新生代分區,混合回收會收集所有的新生代分區以及部分老生代分區,而FullGC則是對所有的分區處理。簡單了解這些后,我們對所有引用關系分類分析:
- 分區內部間的引用:無需記錄引用關系,前文所說,Heap Region是內存分配和回收的最小單位,要么都回收,要么都不回收
- 新生代分區到新生代分區的引用:無需記錄引用關系,無論哪種類型的回收,新生代都會全量回收
- 新生代分區到老年代分區的引用:無需記錄引用關系,
- 老年代分區到新生代分區的引用:需要記錄引用關系,新生代回收時,有兩種根,一種是棧空間/全局變量的引用,另一種便是老年代到新生代的引用
- 老年代分區到老年代分區的引用:需要記錄引用關系,混合回收時可能只回收部分Region,需要記錄引用關系,快速找到活躍對象
記憶集記錄的引用關系具體什么時候會使用
參考「第三章:垃圾回收過程」的「轉移(垃圾回收)」小節
記憶集寫屏障
記憶集其作用是HR間的引用關系,為保證其正確性,那么當引用關系變化時,我們需要及時更新記憶集,而記憶集寫屏障則能幫我們完成這個工作。其工作的偽代碼如下所示:
def evacuation_write_barrier(obj,field,newobj):check=obj^newobj # 異或check=check>>LOG_OF_HEAP_REGION_SIZE # 判斷高位if newobj==Null:check=0if check==0:returnif not is_dirty_card(obj):to_dirty(obj) # 標記為dirty cardenqueue($current_thread.rs_log,obj)*field=newobjobj為引用對象的地址,newobj為被引用對象的地址,field為obj的成員變量,上邊這段代碼的背景是obj的filed字段重新修改引用為newObj,所以obj新增了一個對其它分區的引用關系,需要通過寫屏障記錄。
解釋一下這段代碼干了什么事情,2~7行用于過濾同個HR間的引用,通過兩個對象間的地址的異或(XOR),最終使異或結果左移LOG_OF_HEAP_REGION_SIZE,從而通過判斷高位是否一致,來判斷被引用對象和引用對象是否在同一個分區,如果是一個分區則不需要記錄引用關系的變化了。
LOG_OF_HEAP_REGION_SIZE:Heap Region大小以2為底的對數在9~11行,用來將對象對應卡片,加入dirty_card_queue,簡稱DCQ,DCQ由線程持有,當DCQ慢了之后,會放入一個全局的DCQS隊列,最終會由Refine線程遍歷其中的DCQ,更新Rset的索引關系,其過程如下所示
Refine線程:專門用于處理RSet更新的線程,Refine線程會和Mutator線程并發執行,由于G1在真正執行轉移(垃圾回收)階段時,會強制更新一次Rset,所以Refine線程的主要作用是提前處理RSet的更新,避免轉移(垃圾回收)時停頓時間過長第二章:對象分配過程
快速分配
快速分配指的是基于線程本地分配緩沖區(Thread Local Allocation Buffer)的分配。
通常來說,JVM堆是所有線程的共享區域,因此,從JVM堆空間分配對象時,必須鎖定整個堆,以免被其它線程中斷影響,而為了避免資源競爭降低對象分配效率,TLAB通過給每個線程分配一個緩沖區,來使其進行快速無鎖分配。
?對象分配流程圖
當一個對象快速分配失敗時(通常是TLAB剩余空間不足以分配該對象),線程會視TLAB使用情況決定是否要將這個TLAB歸還到堆空間中去,并再申請一個TLAB,如果判斷需要這么做時,則會在TLAB分配成功后,再次嘗試快速分配,否則則進入慢速分配。
那么什么情況,JVM會判斷該線程需要再申請一個TLAB呢?實際上虛擬機會維護一個refill_waste的值,當對象在TLAB分配對象失敗時,如果對象大小大于refill_waste,則讓其直接進行在堆分配,如果對象大小小于refill_waste,則為其重新分配一個TLAB,將對象分配在新TLAB上。refill_waste的值可用通過TLABRefillWasteFraction,默認值是64,也就是說默認refill_waste=TLABSize/64
慢速分配
快速分配失敗則進入慢速分配
大對象分配
大對象分配也是慢速分配的一種,步驟如下
- 嘗試垃圾回收,同時啟動并發標記。
- 嘗試開始分配對象,對于大對象分為兩類,一類是大于HeapRegionSize的一半,但是小于HeapRegionSize,即一個完整的堆分區可以保存,則直接從空閑列表直接拿一個堆分區,或者分配一個新的堆分區。另一類則是不僅大于HeapRegionSize的一半,還大于HeapRegionSize,即連續對象,需要多個堆分區,思路同上,但是處理的時候需要加鎖。
- 如果失敗再次嘗試垃圾回收,之后再分配。
- 最終成功分配或者失敗達到一定次數,則分配失敗
第三章:垃圾回收過程
Garbage Collection Cycle
前文我們說過G1的GC有三種:新生代GC、混合GC、Full GC。不過按階段來的話,G1的回收只有兩個階段,Young-Only和Space Relcaimation階段。
在Young Only階段,G1只會回收新生代內存,即新生代回收,在Space Reclamation階段,G1除了會全量回收新生代內存,還會回收老年代區域,即混合回收。Full GC是一種特殊的兜底回收邏輯,此處不考慮進來。
所以G1的垃圾回收其實不是我們所想的Young GC和Mixed GC穿插進行,而是Young GC 持續一段時間,Mixed GC 再持續一段時間。
Garbage Collection Cycle概覽圖
那么G1如何判斷什么時候進入Space Reclamation階段呢?參照上圖,當老年代堆內存占比超過一定閾值時,G1會開始并發標記,當并發標記的最后一個階段Clean Up階段完成后,G1則會進入Space Reclamation階段,進行Mixed GC。
并發標記
并發標記的時機是在YGC后,只有達到InitiatingHeapOccupancyPercent閾值后,才會觸發并發標記。InitiatingHeapOccupancyPercent默認值是45。
并發標記階段分為4個子階段
1、初始標記階段(Initial Mark)
負責標記所有直接可達的根對象(棧對象、全局對象、JNI對象等),初始標記伴隨一次YGC,因此初始標記需要將Mutator線程暫停,也就是需要一個STW的時間。
2、并發標記子階段(Concurrent Start)
根據新生代的Survivor分區以及老生代的RSet開始并發標記存活對象。
3、再標記子階段(Remark)
再標記(Remark)是最后一個標記階段。由于并發標記子階段時,Mutator線程仍在不斷修改對象引用關系,因此在該階段中,G1需要一個STW,找出所有未被訪問的存活對象。
4、清理子階段(Clean Mark)
再標記階段之后進入清理子階段,也是需要STW的。清理子階段主要執行以下操作:
- 統計存活對象,統計的結果將會用來排序分區,以用于下一次的CSet的選擇;根據SATB算法,需要把新分配的對象,即不在本次并發標記范圍內的新分配對象,都視為活躍對象。
- 交換標記位圖,為下次并發標記準備。
- 重置RSet,此時老生代分區已經標記完成,如果標記后的分區沒有引用對象,這說明引用已經改變,這個時候可以刪除原來的RSet里面的引用關系。
- 把空閑分區放到空閑分區列表中;這里的空閑指的是全都是垃圾對象的分區;如果分區還有任何分區活躍對象都不會釋放,真正釋放是在混合GC中。
?并發標記流程圖
并發標記階段完成后,即能夠獲得所有候選的回收集,G1會根據其回收價值進行排序,在MixedGC時,在滿足期望最大停頓時間的前提下,篩選最具有回收價值的回收集
轉移(垃圾回收)
轉移是Young GC和Mixed GC都會執行的過程,是我們常說的垃圾回收。
在并發標記完成后,G1能篩選出所有候選的回收集,并根據用戶定義的期望最大停頓時間,篩選本次轉移真正的回收集(CSet),標記回收集中的存活對象,將這些對象轉移,完成垃圾回收。
Young GC 的回收只包含全量新生代區域,意味著Young GC 不需要耗費時間來篩選回收集,整個新時代都是回收集,Mixed GC 由于除了回收全量新生代區域,還會回收部分老年代區域,所以需要耗費時間來篩選回收集既然需要標記存活對象,那么就需要從根引用進行可達性分析,來進行標記。
在轉移過程中,除了會使用??臻g、全局變量等根,還會將CSet中的記憶集作為根,進行掃描。
?轉移(垃圾回收)流程圖
如果此刻G1處于Space Reclamation階段,則一定不需要進行并發標記,因為進入Space Reclamation階段,意味著已完成并發標記,Space Reclamation階段在回收到一定閾值才會停下,再次進入Young-Only階段轉移其核心邏輯此處使用偽代碼進行表示(以下偽代碼摘抄自《深入Java虛擬機:JVM G1GC的算法與實現》,本人主要負責解釋說明):
def evacuate_roots():for r in $roots: # 遍歷root集合,if is_into_collection_set(*r):*r=evacuate_obj(r) # 轉移根對象到空閑區域force_update_rs() # 強制更新RSetfor regionin $collection_set: # 遍歷回收集for card in region.rs_cards: # 遍歷Region記憶集中的卡頁scan_card(card)def scan_card(card):for obj in objects_in_card(card): # 遍歷卡頁中的對象if is_marked(obj): # 如果該對象已經標記了,就是說該對象是存活的for child in children(obj): # 遍歷其引用if is_into_collection_set(*child): # 如果引用的對象在回收集中*child=evacuate_obj(child) #轉移對象到空閑區域def evacuate_obj(ref):from=*ref if not is_marked(from): # 如果待轉移對象非存活對象,返回return fromif from.forwarded: # 如果待轉移對象已經轉移,重定向地址為轉移后地址,并返回轉移后地址add_reference(ref,from.forwarded)return from.forwardingto=allocate($free_region,from.size) # 分配內存空間,用于轉移待轉移對象copy_data(new,from,from.size) # 轉移數據from.forwarding=to # 重定向引用from.forwarded=True # 標記對象已轉移for child in children(to): # 掃描待轉移對象的引用關系,加入到隊列中,待后續掃描標記if is_into_collection_set(*child):enqueue($evacuate_queue,child)else:add_reference(child,*child)add_reference(ref,to)return to第四章:GC日志解讀
這里以截取的一段GC日志為例,幫助各位理解GC日志
# 觸發并發標記,原因:大對象分配,老年代占比超過IHOP閾值 # occupancy指老年代占有內存大小,allocation request指本次對象分配所需申請內存 # threshold指閾值 [gc,ergo,ihop ] Request concurrent cycle initiation (occupancy higher than threshold) occupancy: 18790481920B allocation request: 16777248B threshold: 18790481920B (50.00) source: concurrent humongous allocation [safepoint ] Application time: 0.1052313 seconds # 看名字也能看得出來,collect for allocation [safepoint ] Entering safepoint region: G1CollectForAllocation [gc,ergo ] Request concurrent cycle initiation (requested by GC cause). GC cause: G1 Humongous Allocation [gc,heap ] GC(19615) Heap before GC invocations=19615 (full 0): garbage-first heap total 36700160K, used 29863278K [0x00007f98dd000000, 0x00007fa19d00000) [gc,heap ] GC(19615) region size 16384K, 705 young (11550720K), 34 survivors (557056K) [gc,heap ] GC(19615) Metaspace used 580660K, capacity 592798K, committed 598784K, reserved 600064K # 啟動并發標記 [gc,ergo ] GC(19615) Initiate concurrent cycle (concurrent cycle initiation requested) # 伴隨一次YoungGC [gc,start ] GC(19615) Pause Young (Concurrent Start) (G1 Humongous Allocation) [gc,task ] GC(19615) Using 24 workers of 24 for evacuation # TLAB日志信息 # thrds:線程數 # refills:refill的總次數 這里是91694,max表示一個周期內的最大值 # slow allocs: 發生慢速分配的次數 # waste:由三個部分組成,gc、slow、fast # gc:發生GC時還沒有使用的TLAB空間 # slow:暫時不清楚什么意思 # fast:暫時不清楚什么意思 [gc,tlab ] GC(19615) TLAB totals: thrds: 2870 refills: 91694 max: 1434 slow allocs: 11949 max 964 waste: 1.9% gc: 154439632B max: 4864808B slow: 60164832B max: 903784B fast: 0B max: 0B [gc,alloc,region ] GC(3308) Mutator Allocation stats, regions: 1139, wasted size: 1789K ( 0.0%) # 期望的survivor區占有空間,new threshold 15 表示當前對象晉升年齡為15,max threshold 15 表示最大對象晉升年齡為15 [gc,age ] GC(3308) Desired survivor size 1249902592 bytes, new threshold 15 (max threshold 15) # Choose CSet,由于這是一次young gc,所以Cset全是年輕代,耗時為0 [gc,ergo,cset ] GC(3308) Finish choosing CSet. old: 0 regions, predicted old region time: 0.00ms, time remainin: 104.69 [gc,refine ] Activated worker 0, on threshold: 666, current: 709 # GC結束階段信息統計,通常不用關注,打出這一行日志說明本次GC已經接近尾聲 [gc,task,stats ] GC(3308) GC Termination Stats [gc,task,stats ] GC(3308) elapsed --strong roots-- -------termination------- ------waste (KiB)------ [gc,task,stats ] GC(3308) thr ms ms % ms % attempts total alloc undo [gc,task,stats ] GC(3308) --- --------- --------- ------ --------- ------ -------- ------- ------- ------- [gc,task,start ] G1 Service Thread (Periodic GC Task) (run) [gc,task ] G1 Service Thread (Periodic GC Task) (run) 0.074ms (cpu: 0.035ms) [gc,task,stats ] GC(3308) 8 110.28 82.40 74.72 0.01 0.01 1 0 0 0 [gc,task,stats ] GC(3308) 5 110.36 82.41 74.67 0.02 0.02 1 0 0 0 [gc,task,stats ] GC(3308) 2 110.42 82.45 74.67 0.02 0.02 1 0 0 0 [gc,task,stats ] GC(3308) 18 110.37 82.75 74.98 0.02 0.02 1 0 0 0 [gc,task,stats ] GC(3308) 6 110.45 82.41 74.62 0.01 0.01 1 0 0 0 [gc,task,stats ] GC(3308) 15 110.43 82.42 74.64 0.01 0.01 1 1 1 0 [gc,task,stats ] GC(3308) 0 110.54 82.46 74.60 0.01 0.01 1 0 0 0 [gc,task,stats ] GC(3308) 3 110.54 82.41 74.55 0.02 0.02 1 49 49 0 [gc,task,stats ] GC(3308) 1 110.58 82.46 74.57 0.02 0.01 1 0 0 0 [gc,task,stats ] GC(3308) 19 110.52 82.36 74.52 0.01 0.01 1 6 6 0 [gc,task,stats ] GC(3308) 12 110.58 82.40 74.51 0.02 0.02 1 0 0 0 [gc,task,stats ] GC(3308) 22 110.41 84.69 76.70 0.01 0.01 1 150 150 0 [gc,task,stats ] GC(3308) 9 110.63 82.55 74.62 0.01 0.01 1 313 313 0 [gc,task,stats ] GC(3308) 14 110.64 84.66 76.52 0.02 0.02 1 2 2 0 [gc,task,stats ] GC(3308) 4 110.72 82.41 74.43 0.02 0.02 1 0 0 0 [gc,task,stats ] GC(3308) 16 110.68 82.29 74.35 0.01 0.01 1 14 14 0 [gc,task,stats ] GC(3308) 20 110.68 84.77 76.58 0.01 0.01 1 0 0 0 [gc,task,stats ] GC(3308) 10 110.78 88.72 80.09 0.02 0.01 1 0 0 0 [gc,task,stats ] GC(3308) 7 110.83 82.40 74.35 0.02 0.02 1 7 7 0 [gc,task,stats ] GC(3308) 13 110.84 82.38 74.33 0.02 0.01 1 2 2 0 [gc,task,stats ] GC(3308) 21 110.82 82.70 74.63 0.01 0.01 1 0 0 0 [gc,task,stats ] GC(3308) 23 110.47 82.01 74.24 0.01 0.01 1 0 0 0 [gc,task,stats ] GC(3308) 11 110.92 82.38 74.27 0.01 0.01 1 5 5 0 [gc,task,stats ] GC(3308) 17 110.92 85.26 76.86 0.01 0.01 1 1 1 0 # 啟用Redirty任務,保證引用關系無誤 [gc,ergo ] GC(3308) Running G1 Clear Card Table Task using 24 workers for 55 units of work for 1755 regions. [gc,ref ] GC(3308) Skipped phase1 of Reference Processing due to unavailable references # 釋放CSet [gc,ergo ] GC(3308) Running G1 Free Collection Set using 24 workers for collection set length 1188 # 大對象分配信息,這里解釋一下下邊這一行日志: # 312號HR分區為大對象分區,大小為16M,起始地址為0x00007f5321000000,Rset更新過1次,代碼塊長度為0,is marked 0表示被標記,不能被回收,且是數組類型 [gc,humongous ] GC(3308) Live humongous region 312 object size 16777248 start 0x00007f5321000000 with remset 1 code roots 0 is marked 0 reclaim candidate 0 type array 1 [gc,humongous ] GC(3308) Live humongous region 378 object size 8388632 start 0x00007f5363000000 with remset 0 code roots 0 is marked 0 reclaim candidate 0 type array 0 [gc,humongous ] GC(3308) Live humongous region 410 object size 16777240 start 0x00007f5383000000 with remset 0 code roots 0 is marked 0 reclaim candidate 0 type array 0 [gc,humongous ] GC(3308) Live humongous region 459 object size 16777240 start 0x00007f53b4000000 with remset 0 code roots 0 is marked 0 reclaim candidate 0 type array 0 [gc,humongous ] GC(3308) Live humongous region 461 object size 16777248 start 0x00007f53b6000000 with remset 1 code roots 0 is marked 0 reclaim candidate 0 type array 1 [gc,humongous ] GC(3308) Live humongous region 572 object size 8388632 start 0x00007f5425000000 with remset 0 code roots 0 is marked 0 reclaim candidate 0 type array 0 [gc,humongous ] GC(3308) Live humongous region 579 object size 134217752 start 0x00007f542c000000 with remset 2 code roots 0 is marked 0 reclaim candidate 0 type array 0 [gc,humongous ] GC(3308) Live humongous region 669 object size 16777240 start 0x00007f5486000000 with remset 0 code roots 0 is marked 0 reclaim candidate 0 type array 0 [gc,humongous ] GC(3308) Live humongous region 677 object size 8388632 start 0x00007f548e000000 with remset 0 code roots 0 is marked 0 reclaim candidate 0 type array 0 [gc,humongous ] GC(3308) Live humongous region 678 object size 16777240 start 0x00007f548f000000 with remset 0 code roots 0 is marked 0 reclaim candidate 0 type array 0 [gc,humongous ] GC(3308) Live humongous region 685 object size 8388632 start 0x00007f5496000000 with remset 0 code roots 0 is marked 0 reclaim candidate 0 type array 0 [gc,humongous ] GC(3308) Live humongous region 689 object size 16777240 start 0x00007f549a000000 with remset 0 code roots 0 is marked 0 reclaim candidate 0 type array 0 [gc,humongous ] GC(3308) Live humongous region 691 object size 16777248 start 0x00007f549c000000 with remset 1 code roots 0 is marked 0 reclaim candidate 0 type array 1 [gc,humongous ] GC(3308) Live humongous region 741 object size 8388632 start 0x00007f54ce000000 with remset 0 code roots 0 is marked 0 reclaim candidate 0 type array 0 [gc,humongous ] GC(3308) Live humongous region 742 object size 16777240 start 0x00007f54cf000000 with remset 0 code roots 0 is marked 0 reclaim candidate 0 type array 0 [gc,humongous ] GC(3308) Dead humongous region 747 object size 16762032 start 0x00007f54d4000000 with remset 0 code roots 0 is marked 0 reclaim candidate 1 type array 1 [gc,humongous ] GC(3308) Live humongous region 753 object size 8388632 start 0x00007f54da000000 with remset 0 code roots 0 is marked 0 reclaim candidate 0 type array 0 [gc,humongous ] GC(3308) Live humongous region 754 object size 16777240 start 0x00007f54db000000 with remset 0 code roots 0 is marked 0 reclaim candidate 0 type array 0 [gc,humongous ] GC(3308) Live humongous region 766 object size 12707280 start 0x00007f54e7000000 with remset 1 code roots 0 is marked 0 reclaim candidate 0 type array 1 [gc,humongous ] GC(3308) Live humongous region 770 object size 9692992 start 0x00007f54eb000000 with remset 1 code roots 0 is marked 0 reclaim candidate 0 type array 1 [gc,humongous ] GC(3308) Live humongous region 771 object size 8388632 start 0x00007f54ec000000 with remset 0 code roots 0 is marked 0 reclaim candidate 0 type array 0 [gc,ergo,refine ] GC(3308) Updated Refinement Zones: green: 719, yellow: 2157, red: 3595 # 下邊信息為本次GC各階段的耗時信息 # PS:不是這個時候才執行下邊這些階段,而是執行完了,統計耗時信息 # 預回收階段統計信息 [gc,phases ] GC(3308) Pre Evacuate Collection Set: 0.1ms [gc,phases ] GC(3308) Prepare TLABs: 1.5ms [gc,phases ] GC(3308) Choose Collection Set: 0.0ms [gc,phases ] GC(3308) Humongous Register: 0.1ms # evacuation階段 [gc,phases ] GC(3308) Evacuate Collection Set: 111.1ms // 根結點掃描的耗時信息,其中Min表示24個線程中耗時最短的線程耗時情況,Avg、Max等參數以此類推 [gc,phases ] GC(3308) Ext Root Scanning (ms): Min: 3.5, Avg: 5.1, Max: 5.5, Diff: 2.0, Sum: 123.3, Workers: 24 # 記憶集(RSet)更新 [gc,phases ] GC(3308) Update RS (ms): Min: 7.8, Avg: 19.2, Max: 29.9, Diff: 22.2, Sum: 460.8, Workers: 24 [gc,phases ] GC(3308) Processed Buffers: Min: 13, Avg: 107.6, Max: 221, Diff: 208, Sum: 2583, Workers: 24 # Scan Cards # 這一階段會從DCQ中找出卡片,遍歷其區域中所有對象,用于更新Rset [gc,phases ] GC(3308) Scanned Cards: Min: 3316, Avg: 8767.1, Max: 14417, Diff: 11101, Sum: 210411, Workers: 24 [gc,phases ] GC(3308) Skipped Cards: Min: 0, Avg: 10.3, Max: 23, Diff: 23, Sum: 247, Workers: 24 # 掃碼Rset # 這一階段會將Rset中對象作為根,掃描活躍對象 [gc,phases ] GC(3308) Scan RS (ms): Min: 0.0, Avg: 9.0, Max: 16.2, Diff: 16.2, Sum: 217.0, Workers: 24 [gc,phases ] GC(3308) Scanned Cards: Min: 0, Avg: 4385.7, Max: 8922, Diff: 8922, Sum: 105257, Workers: 24 [gc,phases ] GC(3308) Claimed Cards: Min: 0, Avg: 4892.9, Max: 9695, Diff: 9695, Sum: 117429, Workers: 24 [gc,phases ] GC(3308) Skipped Cards: Min: 0, Avg: 53531.8, Max: 84703, Diff: 84703, Sum: 1284762, Workers: 24 [gc,phases ] GC(3308) Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1, Workers: 24 [gc,phases ] GC(3308) AOT Root Scanning (ms): skipped # 對像拷貝 [gc,phases ] GC(3308) Object Copy (ms): Min: 62.5, Avg: 76.7, Max: 98.7, Diff: 36.2, Sum: 1839.7, Workers: 24 [gc,phases ] GC(3308) Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.4, Workers: 24 [gc,phases ] GC(3308) Termination Attempts: Min: 1, Avg: 1.0, Max: 1, Diff: 0, Sum: 24, Workers: 24 [gc,phases ] GC(3308) GC Worker Other (ms): Min: 0.1, Avg: 0.5, Max: 0.8, Diff: 0.7, Sum: 11.2, Workers: 24 [gc,phases ] GC(3308) GC Worker Total (msG): Min: 110.3, Avg: 110.6, Max: 110.9, Diff: 0.6, Sum: 2655.0, Workers: 24 # 善后工作 [gc,phases ] GC(3308) Post Evacuate Collection Set: 3.7ms [gc,phases ] GC(3308) Code Roots Fixup: 0.0ms [gc,phases ] GC(3308) Clear Card Table: 2.1ms [gc,phases ] GC(3308) Reference Processing: 0.3ms [gc,phases,ref ] GC(3308) Reconsider SoftReferences: 0.0ms [gc,phases,ref ] GC(3308) SoftRef (ms): skipped [gc,phases,ref ] GC(3308) Notify Soft/WeakReferences: 0.1ms [gc,phases,ref ] GC(3308) SoftRef (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0, Workers: 1 [gc,phases,ref ] GC(3308) WeakRef (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0, Workers: 1 [gc,phases,ref ] GC(3308) FinalRef (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0, Workers: 1 [gc,phases,ref ] GC(3308) Total (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0, Workers: 1 [gc,phases,ref ] GC(3308) Notify and keep alive finalizable: 0.1ms [gc,phases,ref ] GC(3308) Balance queues: 0.0ms [gc,phases,ref ] GC(3308) FinalRef (ms): Min: 0.0, Avg: 0.1, Max: 0.1, Diff: 0.1, Sum: 1.5, Workers: 24 [gc,phases,ref ] GC(3308) Notify PhantomReferences: 0.1ms [gc,phases,ref ] GC(3308) PhantomRef (ms): Min: 0.1, Avg: 0.1, Max: 0.1, Diff: 0.0, Sum: 0.1, Workers: 1 [gc,phases,ref ] GC(3308) SoftReference: [gc,phases,ref ] GC(3308) Discovered: 0 [gc,phases,ref ] GC(3308) Cleared: 0 [gc,phases,ref ] GC(3308) WeakReference: [gc,phases,ref ] GC(3308) Discovered: 194 [gc,phases,ref ] GC(3308) Cleared: 536 [gc,phases ] GC(3308) Weak Processing: 0.1ms [gc,phases ] GC(3308) JVMTI weak processing: 0.0ms [gc,phases ] GC(3308) JFR weak processing: 0.0ms [gc,phases ] GC(3308) JNI weak processing Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1, Workers: 24 [gc,phases ] GC(3308) VM weak processing Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.9, Workers: 24 [gc,phases ] GC(3308) Merge Per-Thread State: 0.1ms [gc,phases ] GC(3308) Code Roots Purge: 0.0ms [gc,phases ] GC(3308) Redirty Cards: 0.4ms [gc,phases ] GC(3308) DerivedPointerTable Update: 0.7ms [gc,phases ] GC(3308) Free Collection Set: 0.5ms [gc,phases ] GC(3308) Humongous Reclaim: 0.2ms [gc,phases ] GC(3308) Start New Collection Set: 0.0ms [gc,phases ] GC(3308) Resize TLABs: 0.3ms [gc,phases ] GC(3308) Resize Heap After Collection: 0.0ms [gc,phases ] GC(3308) Other: 4.3ms # 各區域變化情況 [gc,heap ] GC(3308) Eden regions: 1139->0(1136) [gc,heap ] GC(3308) Survivor regions: 49->50(149) [gc,heap ] GC(3308) Old regions: 784->785 [gc,heap ] GC(3308) Humongous regions: 53->51 [gc,metaspace ] GC(3308) Metaspace: 534929K->534929K(561152K) [gc,heap ] GC(3308) Heap after GC invocations=3309 (full 0): garbage-first heap total 36700160K, used 14501693K [0x00007f51e9000000, 0x00007f5aa9000000) [gc,heap ] GC(3308) region size 16384K, 50 young (819200K), 50 survivors (819200K) [gc,heap ] GC(3308) Metaspace used 534929K, capacity 545739K, committed 559360K, reserved 561152K # 本次GC總耗時信息 [gc ] GC(3308) Pause Young (Concurrent Start) (G1 Humongous Allocation) 32394M->14161M(35840M) 119.004ms [gc,cpu ] GC(3308) User=2.45s Sys=0.11s Real=0.11s [safepoint ] Leaving safepoint region感言
在本篇文章中,主要向各位介紹來G1的機制原理,篇幅與時間有限,無法在此詳述G1調優相關內容,如果大家對此比較感興趣,后續可能會另寫一篇文章,專門介紹G1調優相關的內容,比如說如何設置進入Space Reclamation的閾值,如何控制選擇Cset,如何控制TLAB重新申請的時機,GC某個階段耗時過長如何嘗試調優等
參考資料
HotSpot Virtual Machine Garbage Collection Tuning Guide
《JVM G1源碼分析和調優》
《深入Java虛擬機:JVM G1GC的算法與實現》
《實戰Java虛擬機:JVM故障診斷與性能優化》
總結
以上是生活随笔為你收集整理的向前迈进!走入GC世界:G1 GC原理深入解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: px转rem的详细解释和用法
- 下一篇: Mac Scrcpy无线连接