以下的一些理論主要用于個人的回顧使用,如需看案例,可以直接跳到下面的應用部分。
G1收集器的處理方式也不是一蹴而就的,從它身上還是可以看到很多之前收集器的身影在上面。比如:邏輯分代、晉升、複制、并行清理、并發分段标記(CMS)等思想中借鑒而來的,較适合大記憶體的機器應用,比如8c16G。之是以要用大記憶體,那是因為它的主要核心還是空間換時間的做法。核心主要有以下幾點
- Region區域的劃分搭配CSET使得單次gc關注的容量變小
- RSet和Cardtable使得标記過程掃描空間變小
- 三色标記和SATB實作增量标記
- 在MIXED GC中對于垃圾空間清理的取舍,單次清理的垃圾變小,垃圾占比小的region不清理(Region活躍度)
基本概念
- Cardtable: Cardtable是為了減少掃描區域而設計的資料結構,早在G1出現之前就有應用,在分代的思想中,就有老年代擔保配置設定空間這種機制,那麼問題就來了,如果對象配置設定到老年代,而YGC隻掃描新生代,是否可行呢 ? 老年代中的對象對年輕代的對象有引用,豈不是會發生誤殺新生代對象這種事情麼(隻掃描新生代不可達,掃描了老年代發現可達。)? 但是,全部掃描整個堆空間無疑是性能非常差的,此時,CardTable就派上了用場。**它是一種記錄了引用關系的資料結構,在分代中記錄的是老年代對新生代的引用,而在G1中則是記錄了Region的對象之間的引用關系。以這種方式減少了掃描的範圍,提高性能,解決可能的誤殺問題。**詳情請檢視 https://segmentfault.com/a/1190000004682407
- Region: Region是G1垃圾收集器堆記憶體中的記憶體配置設定機關,确切的說,它是一種資料結構。因為在它的内部還有子結構Rset和一些Region的屬性。根據分代分為Eden、Sur、Old3種Region分類,根據大小又單獨定義了Humongous,當對象大小>=所設定的Region的一半,Region會被标記為Humongous對象,并且直接在老年代配置設定,避免了晉升copy的耗時。相關圖示如下:
- RSet: Rset全稱為Remenbered Set,它是Region的一個子結構,它記錄了目前Region的對象被其他哪些Region引用了,**本質結構是一個hashtable, key是對目前Region有引用關系的其他Region,而Value則是一個CardTable的描述結構。**Cardtable可以檢視上面的概念描述.為了便于了解,我将我了解的java僞代碼和對應貼圖貼在下面.
//- 為新對象建立一個Region的僞代碼 Region RegionA = new Region(); Table rset = new HashTable<Region,Cardtable>(); RegionA.setRSet(rest) //- 配置設定對象時,如有跨區域引用,則有對應的cardtable和rset的設定 CardtableB.setdirtyCard(y) rset.put(regionB,CardtableB); CardtableC.setdirtyCard(y) rset.put(regionC,CardtableC); //- 設定對象,發生引用關系變更時則可能有 Cardtable.setDirtyCard(n);
- RegionB和RegionC中有對象引用了RegionA的對象。
- Cardtable B描述了RegionB中哪些空間可能對RegionA有引用關系.
- CSet: CSet是單次GC中的中間結構,它儲存單次GC(包含YOUNG GC和MIXED GC)要處理的Region集合.在GC之後,這些Region中存活的對象會copy到Sur Region中或晉升至Old Region中。值得注意的是CSet中的Region會随着使用者所設定的預期停頓時間而變更。預期停頓時間越短,cset集合中的Region則可能越少,反之正相反。相關圖解,請在下面的GC過程圖解中關注
- 三色标記算法:GC 開始運作前所有的對象都是白色。當GC 開始運作,所有從根能到達的對象都會被标記。GC 隻是發現了這樣的對象,但還沒有搜尋完它們,是以這些對象就 成了灰色對象。當其所有的子對象(也就是字段)都被塗成 灰色時,對象就會被塗成黑色。 當 GC 結束時已經不存在灰色對象了,活動對象全部為黑色,垃圾則為白色。 這就是三色标記算法的概念。摘抄出自:https://segmentfault.com/a/1190000039300766,該部落客總結的很棒,在此不再贅述了
- SATB: (SNAPSHOT AT THE BEGINNING.): **增量式GC算法,服務于最終标記階段,其本質是記錄并發标記階段開始到并發标記階段結束這段時間内所産生的引用關系變更。**由于三色标記的算法。使得黑色的對象(認為已經完成了搜尋),不會再次去搜尋其引用關系。SATB是引用寫屏障的方式解決該問題,既在位元組碼中插入對應寫屏障,在棧上配置設定對象時,也在GC上進行相應标記。
GC過程
- 初始标記:隻是标記GC ROOT所直接引用的對象。比如棧中引用到的對象和靜态引用對象。這些對象所在的Region放入CSET中,這些REGION我們又稱為ROOT Region,這個階段是要STW的,但是所掃描的範圍較小,其實,這個階段我們就已經享受到了G1給我們動态調整Eden區域的福利。要知道Eden越小、stw越短
- 并發标記:這個階段是三色标記的主要标記期,它的執行過程是通過ROOT REGION的RSet去關聯其他所有可以關聯REGION中的對象,以此來提高效率。本階段所掃描标記到的Region也累加性的放入CSET中。這個階段是非STW的,會和使用者線程一起運作。但是,這個階段不會太長,過長的并行會導緻下一階段需要增量标記的容量過高。要知道下一階段是STW的,這等于是變相損耗
- 重新标記:這個階段主要是處理并發階段所産生的增量引用,這個增量引用,需要通過SATB來彌補三色标記法的漏洞,具體推薦大家看這篇文章,通俗易懂https://segmentfault.com/a/1190000039300766,增量标記雖然不是第一次提出,但是這也是G1的優化點,因為它和CMS的算法不同,同樣,這個階段也需要STW
- 複制清理:複制清理這個階段主要的優化點是“G1為了避免清理複制所産生的耗時,在Region的垃圾占用率上的做了取舍。即:當OLD區單個REGION達到清理門檻值才會去清理,否則,留到下一次進行清理。但是YOUNG和SUR區域是會全部清理不可達Region的”
G1應用
相關參數解析
- -XX:MaxGCPauseMillis=200:預期停頓其實是一種動态設定機制,它其實是通過動态調整對應區域大小來擷取stw和吞吐的算法,需要注意的是,如果你顯示的指定了 -Xmn 選項或 -XX:NewRatio,那麼,這個預期将失效!,比如新生代的Eden和Sur大小的過程。其實GC的本質沒有變更,掃描越大的空間,必然導緻STW變長,而掃描越小的空間,會增加對應的頻率,進而影響吞吐。G1的這種做法雖然屏蔽了相應的參數設定複雜度。**但是,實際使用的使用不建議将該值設定過小或過大,比如:設定100ms,那也不是每次的GC時間都在這個時間段。此值設定過小,引起的頻率飙升,在并發高時,甚至導緻應用不可用。請謹慎設定!!**在stw停頓時長和掃描空間大小之間做取舍。-XX:GCTimeRatio參數,同樣是一個預期吞吐的參數。我想,其原理MaxGCPauseMillis大緻相同,本篇不過多說明。感興趣的同學可私下了解。
- -XX:+G1SummarizeRSetStats :如果CSets包含許多攜帶coarsened RSets的Region(注意,“coarsening of RSets”是根據RSets貫穿不同級别顆粒度的過渡期定義的),然後你會看到掃描RSets消耗時間的增長。GC階段這些掃描時間就是GC日志裡面的“Scan RS (ms)”。如果RSets掃描時間相當于GC階段總時間很高,或者你的應用中它們表現很高,然後通過使用診斷選項**-XX:+G1SummarizeRSetStats請觀察你的 Young GC 日志輸出的“Did xyz coarsenings”(你可以通過設定-XX:G1SummarizeRSetStatsPeriod=period**指定周期頻率報告(GCs的數量))。from https://segmentfault.com/a/1190000007815623
- -XX:InitiatingHeapOccupancyPercent:簡單來說,是老年代達到堆空間占用的多少開始MIXED GC。該值如果設定過小,則會一直循環觸發MIXED GC。引用說明:通過-XX:G1HeapWastePercent指定觸發Mixed GC的堆垃圾占比,預設值5%,也就是在全局标記結束後能夠統計出所有Cset内可被回收的垃圾占整對的比例值,如果超過5%,那麼就會觸發之後的多輪Mixed GC,如果不超過,那麼會在之後的某次Young GC中重新執行全局并發标記。可以嘗試适當的調高此門檻值,能夠适當的降低Mixed GC的頻率。 https://zhuanlan.zhihu.com/p/181305087
- -XX:G1HeapWastePercent:如果ROOT REGION标記結束後,垃圾占比超過堆的X%,那麼,此時,啟用mixed gc。引用原文通過-XX:G1HeapWastePercent指定觸發Mixed GC的堆垃圾占比,預設值5%,也就是在全局标記結束後能夠統計出所有Cset内可被回收的垃圾占整對的比例值,如果超過5%,那麼就會觸發之後的多輪Mixed GC,如果不超過,那麼會在之後的某次Young GC中重新執行全局并發标記。可以嘗試适當的調高此門檻值,能夠适當的降低Mixed GC的頻率。https://zhuanlan.zhihu.com/p/181305087
- -XX:SoftRefLRUPolicyMSPerMB:當軟引用占比達到{X} M,就開始回收掉這些軟引用,大部分在于業務code中有反射生成的多個軟引用占用記憶體空間。多表現在META SPACE的占比。這個值不能設定為0,也不能太小。它是讓JVM提前優化反射生成的類。個人不推薦設定。由JVM自己控制。出自:https://blog.csdn.net/qiang_zi_/article/details/100700784
日志解析
2021-08-23T15:14:35.787-0800: [GC pause (G1 Evacuation Pause) (young) 134.253: [G1Ergonomics (CSet Construction) start choosing CSet, _pending_cards: 0, predicted base time: 10.39 ms, remaining time: 139.61 ms, target pause time: 150.00 ms]
134.253: [G1Ergonomics (CSet Construction) add young regions to CSet, eden: 574 regions, survivors: 0 regions, predicted young region time: 0.59 ms]
134.253: [G1Ergonomics (CSet Construction) finish choosing CSet, eden: 574 regions, survivors: 0 regions, old: 0 regions, predicted pause time: 10.97 ms, target pause time: 150.00 ms]
, 0.0937042 secs]
[Parallel Time: 20.4 ms, GC Workers: 8]
[GC Worker Start (ms): Min: 134254.7, Avg: 134257.3, Max: 134259.1, Diff: 4.4]
[Ext Root Scanning (ms): Min: 0.0, Avg: 0.1, Max: 0.7, Diff: 0.7, Sum: 0.8]
[Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Processed Buffers: Min: 0, Avg: 0.1, Max: 1, Diff: 1, Sum: 1]
[Scan RS (ms): Min: 0.0, Avg: 0.1, Max: 0.1, Diff: 0.1, Sum: 0.5]
[Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Object Copy (ms): Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.1, Sum: 0.2]
[Termination (ms): Min: 0.0, Avg: 4.2, Max: 11.3, Diff: 11.3, Sum: 34.0]
[Termination Attempts: Min: 1, Avg: 1.0, Max: 1, Diff: 0, Sum: 8]
[GC Worker Other (ms): Min: 0.0, Avg: 0.5, Max: 3.7, Diff: 3.7, Sum: 4.0]
[GC Worker Total (ms): Min: 0.1, Avg: 4.9, Max: 11.4, Diff: 11.3, Sum: 39.5]
[GC Worker End (ms): Min: 134259.2, Avg: 134262.3, Max: 134269.2, Diff: 10.0]
[Code Root Fixup: 0.0 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 1.1 ms]
[Other: 72.2 ms]
[Choose CSet: 0.0 ms]
[Ref Proc: 70.3 ms]
[Ref Enq: 0.1 ms]
[Redirty Cards: 0.2 ms]
[Humongous Register: 0.0 ms]
[Humongous Reclaim: 0.0 ms]
[Free CSet: 0.6 ms]
[Eden: 9184.0M(9184.0M)->0.0B(9184.0M) Survivors: 0.0B->0.0B Heap: 9185.6M(10.0G)->1682.0K(10.0G)]
[Times: user=0.05 sys=0.01, real=0.10 secs]
- G1自動調整分區大小,展開的cardtable:0,預計需要時間10.39ms,134.253機關為秒,是jvm啟動到現在持續的時間。
- 将GC ROOTS關聯的相關REGION添加到CSET中,整個EDEN區共574個REGION,預計持續0.59ms
- 結束CSET收集階段,預計耗時10.97ms
- Parallel Time: 20.4 ms, GC Workers: 8:并行階段,worker線程8個,以下表示的min,max和avg都是以線程為機關進行描述的。因為每個線程的操作時間都不一樣。
- [GC Worker Start (ms): Min: 134254.7, Avg: 134257.3, Max: 134259.1, Diff: 4.4]:gc worker并行階段開始的時間,這裡的時間是說距離jvm啟動到現在距離的時間,其中,min是線程最早開始時間,max是線程最晚開始時間avg是平均開始時間,這裡的diff是max-min
- Ext Root Scanning:掃描root集合所花費的時間(線程棧、jni、全局變量、系統表等等),花費的時間,其中sum是cpu總共花費的時間
- Update RS:根據上文描述,每個分區都有自己的rset,用來記錄其他region對目前region的引用。但是,在并行收集的過程中,region的rset是一個互斥的區域,由此,rset的更新情況隻能借助另外一個日志來存儲對應的post-write barrier變更,新的應用變更會标記為dirty card,對應的dirty card處理有另外一個線程,在重新标記階段會處理這些log buffer以達到更新rset為最新的記錄
- Processed Buffers:表示并行掃描階段産生了多少個寫屏障的dirty card
- Scan RS:找出rset的用時,找出region中有關聯目前cset的region
- Code Root Scanning:code root指的是jit編譯後代碼裡面引用了heap的對象,掃描該區域花費的時間。
- Object Copy:拷貝gc完成後,存活對象到新的region的耗時
- Termination:gc結束時,已經處理完任務的線程嘗試檢視有沒有待處理的任務(其他線程沒有執行完的),此時,去“竊取”其他線程未完成的任務,這個階段有點類似fork/join線程池的竊取機制
- Termination Attempts:如果一個線程成功竊取了其他線程的工作任務,每次處理完之後,它會重新嘗試竊取或者終止。每一次嘗試終止,這個時間就會增加。
- GC Worker Other:其他未能列出的耗時
- GC Worker Total:每個垃圾收集線程的最小、最大、平均、內插補點總共時間。
- GC Worker End:min表示最早結束的垃圾收集線程結束時該JVM啟動後的時間;max表示最晚結束的垃圾收集線程結束時該JVM啟動後的時間。理想情況下,你希望它們快速結束,并且最好是同一時間結束。
- Code Root Fixup:清理GC期間産生的資料結構耗時
- Code Root Purge:清理更多的資料結構
- Clear CT:清理GC完成之後清理無效的Card Table.
- Other:
- Choose CSet:選擇放入CSET的Region所消耗的時間,該選擇是篩選Region活躍度的過程
- Ref Proc :引用入ReferenceQueues
- Ref Enq:周遊所有的引用,将不能回收的放入pending清單
- Redirty Cards:在回收過程中,被修改的card重新設定為dirty髒卡,以在下次GC之前,重新标記該card
- Humongous Register:大對象配置設定耗時,JDK8u60提供了一個特性,巨型對象可以在新生代收集的時候被回收——通過
設定,預設為true。G1ReclaimDeadHumongousObjectsAtYoungGC
- Humongous Reclaim:做下列任務的時間:確定巨型對象可以被回收、釋放該巨型對象所占的分區,重置分區類型,并将分區還到free清單,并且更新空閑空間大小。
- Free CSet:釋放CSET中的region到freelist
預釋出應用的實踐效果
- 預釋出參數設定
- 之前
-Xms2048m -Xmx2048m -Xmn768m -Xss512K -XX:PermSize=256m -XX:MaxPermSize=256m -XX:SurvivorRatio=8 -XX:InitialTenuringThreshold=8 -XX:MaxTenuringThreshold=8 -XX:ParallelGCThreads=4 -XX:TargetSurvivorRatio=70 -XX:+UseBiasedLocking -XX:-UseAdaptiveSizePolicy -verbose:gc -Xloggc:../gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintAdaptiveSizePolicy -XX:+PrintTenuringDistribution -XX:+PrintReferenceGC -Djava.awt.headless=true -Dsun.net.client.defaultConnectTimeout=60000 -Dsun.net.client.defaultReadTimeout=60000 -Djmagick.systemclassloader=no -Dnetworkaddress.cache.ttl=300 -Dsun.net.inetaddr.ttl=300 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./java_HeapDump.hprof"
- 之後
-Xms2G -Xmx2G -XX:MetaspaceSize=128M -XX:MaxMetaspaceSize=128M -XX:+UseG1GC -XX:G1HeapRegionSize=1M -XX:G1MaxNewSizePercent=90 -XX:MaxGCPauseMillis=150 -XX:+G1SummarizeRSetStats -XX:+ParallelRefProcEnabled -XX:ParallelGCThreads=2 -XX:+UnlockExperimentalVMOptions -XX:+UnlockDiagnosticVMOptions -XX:InitiatingHeapOccupancyPercent=3 -XX:G1HeapWastePercent=1 -XX:SoftRefLRUPolicyMSPerMB=1 -XX:+PrintGCDetails -XX:+PrintAdaptiveSizePolicy -XX:+PrintGCDateStamps -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=52001 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./java_HeapDump.hprof -verbose:gc -Xloggc:../gc.log"
- 更改之前的參數效果
- 更改後的效果
- 結論:參數可用,但在2C4G小記憶體的表現下,G1沒有parallel表現好。
- 單次YGC時間變長,頻率變低,但是,沒有了FULL GC。
生産環境實踐G1的效果
- 更改前的參數
-Xms2048m
-Xmx2048m
-Xmn768m
-Xss512K
-XX:PermSize=256m
-XX:MaxPermSize=256m
-XX:SurvivorRatio=8
-XX:InitialTenuringThreshold=8
-XX:MaxTenuringThreshold=8
-XX:ParallelGCThreads=4
-XX:TargetSurvivorRatio=70
-XX:+UseBiasedLocking
-XX:-UseAdaptiveSizePolicy
-verbose:gc
-Xloggc:../gc.log
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintAdaptiveSizePolicy
-XX:+PrintTenuringDistribution
-XX:+PrintReferenceGC
-Djava.awt.headless=true
-Dsun.net.client.defaultConnectTimeout=60000
-Dsun.net.client.defaultReadTimeout=60000
-Djmagick.systemclassloader=no
-Dnetworkaddress.cache.ttl=300
-Dsun.net.inetaddr.ttl=300
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=./java_HeapDump.hprof"
- 更改後的參數
-Xms10G
-Xmx10G
-XX:+UseG1GC
-XX:MetaspaceSize=512M
-XX:MaxMetaspaceSize=512M
-XX:+UnlockExperimentalVMOptions
-XX:+UnlockDiagnosticVMOptions
-XX:G1HeapRegionSize=16M
-XX:G1MaxNewSizePercent=70
-XX:MaxGCPauseMillis=150
-XX:+G1SummarizeRSetStats
-XX:+ParallelRefProcEnabled
-XX:ParallelGCThreads=8
-XX:InitiatingHeapOccupancyPercent=3
-XX:G1HeapWastePercent=1
-XX:+PrintGCDetails
-XX:+PrintAdaptiveSizePolicy
-XX:+PrintGCDateStamps
-XX:+PrintReferenceGC
-XX:SoftRefLRUPolicyMSPerMB=500
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=./java_HeapDump.hprof
-verbose:gc
-Xloggc:../gc.log
- 更改前的GC效果
- 第一階段更改後的效果(STW 60-140MS)
- 最終更改後的效果圖(STW 10-20ms)
遇到問題和解決過程 JNI Weak Reference
從上圖中,可以看到,第一階段換G1GC以後,效果并沒有那麼明顯。優化前單次GC的時間是10-20ms. 而優化後,居然STW到了60-140ms,這顯然是不能接受的,很多文章說增加-XX:+ParallelRefProcEnabled參數來并行處理引用,但是,我的參數中是有這個的,顯然不是這個問題。于是檢視GC LOG,如下圖:
這個Ref Proc是引用入隊,這其中JNI Weak Reference耗時最長,這個JNI Weak Reference是什麼東西 ? 果斷jmap -histo
- 看到這裡,我隻能黔驢技窮了。問題被擱置,但是,經驗告訴我不能放棄。因為,之前我有過實踐G1,YGC的STW不會這麼長時間!!
- 不能去檢視C語言編寫的jvm内部到底配置設定了什麼對象,翻看網上的一些文章,看到"你假笨"騷操作,居然更改jvm實作,列印出來這東西是什麼。對應的文章:https://www.heapdump.cn/article/62972,文章中說,用java調用了js,我趕緊去看項目,但是,項目中并沒有這樣的實作。
- 沒有辦法,線索斷了,就連笨神都隻能用修改jvm這樣的辦法來找問題。在這樣的情況下,我隻能好好的去看看JNI Reference這東西到底是什麼?試着去聯想。然而,我是幸運的。項目中接入了公司自研的agent監控程式,其實作用C語言調用jvmti來實作相關監控。想到這裡,我基本上就定位了問題。嘗試去解除安裝一台監控。問題得到了解決。在8C16G的G1環境中繼續保持10-20ms的stw時間,但是堆增大到10G空間,減少了對應的GC頻率,并提高了整體吞吐!
參考文獻
https://tech.meituan.com/2016/09/23/g1.html 美團技術團隊
https://www.oracle.com/cn/technical-resources/articles/java/g1gc.html Oracle
https://blog.csdn.net/u012586326/article/details/112343691 東北小狐狸-Hellxz 2020-11-29 15:23:00
https://zhuanlan.zhihu.com/p/43010596 知乎
https://blog.csdn.net/weixin_38007185/article/details/108093716 一直Tom貓 2020-08-19 10:14:01
https://segmentfault.com/a/1190000004682407 cardtable
https://cloud.tencent.com/developer/article/1847053 cset
https://segmentfault.com/a/1190000039300766 三色标記SATB(SNAPSHOT AT THE BEGINNING)
https://zhuanlan.zhihu.com/p/181305087 G1參數解析
https://zhuanlan.zhihu.com/p/74517142 G1日志解析
https://blog.csdn.net/qiang_zi_/article/details/100700784 SoftRefLRUPolicyMSPerMB
https://blog.csdn.net/u010833547/article/details/90289325 SoftRefLRUPolicyMSPerMB