JDK11-G1收集器調優
同時歡迎觀看本人錄得兩個視訊教程:
G1通用推薦設定
G1一般推薦使用它的預設設定,然後設定一個停頓時間和最大堆記憶體的目標。 G1跟別的收集器不一樣,G1預設配置的目標既不是最大化吞吐量也不是最小化停頓時間,而是使用時間相對較短的停頓來達到很高的吞吐量。 但是,G1的這種增量回收記憶體和停頓時間的控制機制不管是對應用執行緒還是對記憶體回收的效率都會導致一些額外的開銷。
如果你更想要高的吞吐量,那就設定相對寬鬆的停頓時間的目標(用 -XX:MaxGCPauseMillis)或者是提供更大的堆記憶體。如果停頓時間更重要,那就修改停頓時間的目標。要避免使用 -Xmn, -XX:NewRatio
從其他收集器在切換到G1
一般來說,當從其他收集器切換到G1的時候,比如CMS,首先刪掉所有的影響GC的引數,只設置停頓時間的目標和整個堆的大小(使用 -Xmx 和可選的 -Xms)
許多對其他收集器在某些方面有用的引數,對G1要麼就沒作用要麼甚至會降低吞吐量來滿足停頓時間的目標。比如如果設定了young區的大小會完全阻止G1調整young區大小來滿足停頓時間的目標。
提高G1的效能
G1被設計成不用做額外的設定就可以提供整體上很好的效能。但是,有時候這種預設的行為和配置並不能提供最優的結果。本節就對如何診斷和改善這些情況給出了一些指導意見。這些指導意見可能只是在特定應用的某個維度上改善GC效能。具體情況要具體分析,應用程式級別的優化可能比虛擬機器的優化帶來更好的效果,比如說減少長期存活的物件可以避免很多不確定的情況出現。
為了方便診斷,G1提供了大量的日誌。一般要從設定 -Xlog:gc*=debug 開始,如果需要的話再細化輸出結果。日誌提供了詳細的GC過程和停頓的資訊,包括GC的型別、在停頓的每個階段所花費的時間等等。
下面的章節描述了一些常見的效能問題。
發生了FullGC
FullGC一般是非常耗時的。old區堆佔有率高導致的FullGC在日誌中通過 Pause Full (Allocation Failure) 這樣的字眼就可以發現,FullGC一般發生在帶有 to-space exhausted 耗盡 標記的evacuation failure之前。
一種FullGC是因為應用分配了太多的物件來不及快速回收導致的,這種情況下併發標記還沒結束來不及到space-reclamation階段。FullGC還有可能是由分配很多大物件引起的。基於這些物件在G1中分分配方式,它們會佔用比預期大小大很多的記憶體。
所以調優目標就是確保併發標記可以按時結束。要麼降低old區記憶體分配的速度,要麼給併發標記更多的執行時間都可以達到這個目標。
G1提供了好幾個引數來更好的處理這種情況:
- 通過 gc+heap=info 這種日誌可以發現堆中大物件佔用的region數量,日誌中這一行 "Humongous regions: X->Y” 中的Y就是大物件佔用的region數量。如果相對old區總的region數來說,這個數值相對偏高,那最好就要減少這個數值。可以使用 -XX:G1HeapRegionSize 調大region的大小來減少大物件佔用的region數。當前的region的大小在日誌開頭有輸出。
- 增大堆的大小。一般這也會增加mark的時間。
- 增加併發標記的執行緒數,通過 -XX:ConcGCThreads這個引數。
- 強制G1提早開始mark。G1會根據之前的應用的行為來自動來決定IHOP的閾值。如果應用的行為變化了,先前的預測就是錯誤的。可以有兩種方式:通過增加自適應IHOP計算的 -XX:G1ReservePercent的值來減少堆的目標占有率提前開始space-reclamation階段,或者是通過 -XX:-G1UseAdaptiveIHOP禁用掉自適應IHOP,然後使用 -XX:InitiatingHeapOccupancyPercent手動設定IHOP。
其他的FullGC一般是應用程式或者是外部的工具導致的。如果是由System.gc()導致的,並且還沒有辦法修改原始碼,可以使用 -XX:+ExplicitGCInvokesConcurrent 來減輕FullGC的可能性,或者是使用 -XX:+DisableExplicitGC 讓虛擬機器完全忽略掉這句程式碼。外部的工具也可能會導致FullGC,如果不要就可以刪掉他們。
大物件碎片
在堆記憶體耗盡之前,為了找到一系列連續的region也會導致FullGC。這種情況可以通過設定 -XX:G1HeapRegionSize 增大region的大小減少大物件佔用的region數量,或者是增加整個堆的大小。極限情況下,雖然看上去還有很多的可用記憶體,但是G1找不到足夠的連續的記憶體來分配物件。如果FullGC也無法回收足夠的可用連續記憶體就會導致虛擬機器退出。這種情況下,只能要麼減少大物件的數量,要麼就增加堆的大小。
停頓時間調優
本節討論如何改善停頓時間太長的問題
System Time和Real-Time
在每一個GC停頓的時候,日誌中的 gc+cpu=info 會包含一行停頓時間是如何花費的資訊,比如:User=0.19s Sys=0.00s Real=0.01s. User time是虛擬機器花費的時間,System time是作業系統花費的時間,Real time是停頓花費的絕對時間。如果System time相對較高,很可能是環境的原因。
System time高導致的幾個問題:
- 虛擬機器從作業系統那裡申請和歸還記憶體會導致不必要的延遲。可以使用 -Xms和-Xmx 把虛擬機器的最大最小記憶體設定為同一個值來避免這種延遲,還可以使用 -XX:+AlwaysPreTouch 來預分配所有的記憶體,這樣把延遲提前到了虛擬機器啟動階段。
- 尤其是在linux中,通過THP技術把小分頁合併成一個大分頁可能會拖慢任意一個程序,而不僅僅是在停頓的時候。因為虛擬機器會分配和佔用大量的記憶體,因為很可能會被作業系統選中而拖慢很長時間。如何來禁用THP要參考你的作業系統的手冊。
- 寫日誌也會拖慢一些時間,因為會有後臺的間斷的任務佔用了寫日誌的磁碟的IO頻寬。可以考慮給你的日誌使用單獨的磁碟或者其他的儲存,比如使用記憶體檔案系統。
另一種需要注意的情況是System time比其他兩個的總和還要長,這說明虛擬機器沒有得到足夠的CPU,主機的負載可能太高了。
引用物件處理時間太長
在引用處理階段(Reference Processing)會展示引用物件處理花費的時間資訊。在引用處理階段,G1會根據飲用物件的型別來更新應用物件的引用。預設,G1使用下面的啟發式演算法並行化引用處理的各個子階段:對每一個引用物件開啟一個單獨的執行緒,這個值受限於 -XX:ParallelGCThreads。可以把 -XX:ReferencesPerThread 設定為0,這樣就會使用所有的可用的執行緒,或者使用 -XX:-ParallelRefProcEnabled完全禁用掉並行處理。
Young-Only階段時間太長
youngGC花費的時間一般跟young區的大小成正比例關係,更確切的說跟young區的回收集合中的要拷貝的存活物件的數量有關。如果這個時間太長,假如是物件拷貝時間長那就減小 -XX:G1NewSizePercent。這會減小young區的最小的大小,會減小停頓時間。
如果手動設定young區的大小帶來別的問題,如果應用的效能尤其是存活物件的數量突然發生了變化,這會導致停頓時間出現毛刺,通過設定 -XX:G1MaxNewSizePercent減小最大young區的大小可以有效減輕這個問題。這個引數限制了young區的最大尺寸,因此也限制了停頓時間內需要處理的物件數量。
MixedGC時間太長
MixedGC主要用來回收old區的記憶體。MixedGC的回收集合包含了young區和old區的region,啟用日誌中的 gc+ergo+cset=trace,就可以得到young區和old區的回收對停頓時間分別做了多大貢獻的資訊。分別對照下young區和old區的可預測的停頓時間。
如果是young區時間太長,可以參考Young-Only階段時間太長。否則,jiu要減少old區的停頓時間,G1提供了下面3個選項:
- 增大 -XX:G1MixedGCCountTarget 把old區的回收分不到更多次GC當中。
- 設定 -XX:G1MixedGCLiveThresholdPercent 避免把那些回收花費時間長的region加入到回收集合。很多時候,存活物件佔有率高的region會花費更多的時間來回收。
- 可以讓space-reclamation儘早結束,這樣G1就不會收集更多的佔用率高的region。這種情況下可以調大 -XX:G1HeapWastePercent。
需要注意,後面的2個引數會減少候選集合的數量,這樣在一個回收階段能回收的空間數量也會減少。這意味著G1有可能並不能回收old區足夠的空間來做後續的操作,但是隨後的space-reclamation可能會把這些垃圾回收掉。
Update RS 和Scan RS時間長
G1為了能收回old區的region,它需要記錄跨region的引用關係,也就是從一個region指向另一個region的引用。跨region的引用集合被叫做region的remember set。當移動region的內容的時候,必須要更新remember set。對remember set的維護幾乎是併發的。為了效能的原因,當應用建立了跨region的引用的時候,G1並不會立即就更新remember set,更新remember set的請求會被推遲並且批量執行。
垃圾收集的時候需要一個完整的remember set,Update RS階段會處理未完成的那些更新RS的請求,Scan RS階段就是在RS中查詢物件之間的引用,移動region中的物件,更新這些物件的應用到新的位置。這兩個階段可能會花費大量時間取決於應用的不同。 通過設定 -XX:G1HeapRegionSize 來調整region的大小,這會影響跨region的引用數量,同時也會影響RS的大小。處理region的RS可能是GC中時間很長的一部分,因此這直接影響了最大停頓時間。含有少量跨region引用的大region在處理RS的時間上會減少,但是大的region也就意味著region中有更多的存活物件要回收,這會增加其他階段的時間。
G1併發處理RS更新,UpdateRS花費的時間大概是最大停頓時間的 -XX:G1RSetUpdatingPauseTimePercent,如果減小這個值,G1會併發的做更多的更新RS的工作。
如果是伴隨著應用分配大物件而造成的假的更新RS時間太長,G1會嘗試批處理這些更新。如果建立這樣的批處理正好在GC之前,那麼GC就需要在更新RS停頓的時間之內處理完所有的這些工作。可以設定 -XX:-ReduceInitialCardMarks 來禁用這種行為來避免發生潛在的這種情況。
ScanRS的時間是由 G1為了保持RS的數量比較小而做的壓縮的數量來決定的。RS在記憶體中的儲存越緊湊,在垃圾回收中要檢索出儲存的值就會花費越多的時間。當更新RS的時候,根據RS的大小,G1會自動做這樣的壓縮,這叫做RS coarsening。在最高級別壓縮的情況下,檢索出實際的資料可能非常的慢。-XX:G1SummarizeRSetStatsPeriod 這個選項結合日誌中的 gc+remset=trace可以表明是否發生了coarsening。如果發生了coarsening,在 Before GC Summary這一塊 Did coarsenings 這一行中的X會出現一個很高的值,-XX:G1RSetRegionEntries 這個選項可以極大地減少這些coarsening的發生。在生產環境中要避免使用這個詳細的RS日誌,因為這些資料會佔用大量的時間。
吞吐量調優
G1預設在吞吐量和停頓時間保持一個平衡。但是,有的場景是需要高吞吐量的。除了前面章節介紹的如何降低停頓時間,還可以減少停頓的頻率,這主要是使用 -XX:MaxGCPauseMillis來調大停頓時間。分帶大小自適應策略會自動的調整young區的大小,這會直接決定停頓的頻率。如果達不到預期的效果,尤其是在space-reclamation階段,可以通過 -XX:G1NewSizePercent 增大最小young區的記憶體來強制G1這麼做。
代表了young區的最大大小的 -XX:G1MaxNewSizePercent ,有些情況下因為限制了young區的大小因此會限制吞吐量。通過日誌中的 region summary輸出中的 gc+heap=info 可以發現。 在這種情況下,Eden區和Survior區的大小之和接近 -XX:G1MaxNewSizePercent,這個時候就要考慮調大 -XX:G1MaxNewSizePercent 。
另一個增加吞吐量的辦法是減少併發的工作量,比如併發的更新RS經常會需要大量的CPU資源。調大 -XX:G1RSetUpdatingPauseTimePercent 這個值會把併發操作的工作移動到GC停頓中。最壞的情況是併發的更新RS可以被禁用,通過設定這幾個引數 -XX:-G1UseAdaptiveConcRefinement -XX:G1ConcRefinementGreenZone=2G -XX:G1ConcRefinementThreads=0。這就會禁用這種機制,並且把所有的RS更新操作移動到下一次GC停頓中。
使用 -XX:+UseLargePages 來啟用大記憶體頁也可以提高吞吐量。關於如何設定大記憶體頁可以參考你的作業系統的文件。
還可以禁用堆大小調整來最小化這部分工作,可以把 -Xms和-Xmx 設定為相同的值。此外,還可以使用 -XX:+AlwaysPreTouch 在虛擬機器啟動的時候讓作業系統使用實體記憶體來支撐虛擬機器的虛擬記憶體(也就是提前把記憶體分配好)。這些操作都能夠讓停頓時間跟預期的更一致。
堆大小調優
跟其他的收集器類似,G1的目標是讓花費在垃圾收集的時間低於 -XX:GCTimeRatio這個值。可以調整這個值來滿足你的需求。
預設值調優
本節講述了關於這個話題的一些預設值和一些命令列引數的額外資訊。
預設值 | 描述 |
---|---|
-XX:+G1UseAdaptiveConcRefinement -XX:G1ConcRefinementGreenZone=< ergo> -XX:G1ConcRefinementYellowZone=< ergo> -XX:G1ConcRefinementRedZone=< ergo> -XX:G1ConcRefinementThreads=< ergo> | 併發RS更新需要這些引數來控制併發執行緒的工作。 這些操作G1都有自適應的預設值, -XX:G1RSetUpdatingPauseTimePercent這個值是GC停頓中花費在處理剩餘工作的時間,可以按需調整這個值。 調整這個值要當心因為可能會引起非常長時間的停頓。 |
-XX:+ReduceInitialCardMarks | 這個引數把併發RS更新跟新物件分配放到一起執行 |
-XX:+ParallelRefProcEnabled -XX:ReferencesPerThread=1000 | -XX:ReferencesPerThread決定了併發度:每N個引用物件都會有一個執行緒參與引用處理,這個值受限於-XX:ParallelGCThreads。如果設定為0,標明最大執行緒數總是-XX:ParallelGCThreads。這個決定了對 java.lang.Ref.* 這些物件例項的處理是否是多個執行緒並行執行的 |
-XX:G1RSetUpdatingPauseTimePercent=10 | 這個值決定了G1花費在更新剩餘RS的UpdateRS階段上的時間佔總的垃圾回收時間的百分比 |
-XX:G1SummarizeRSetStatsPeriod=0 | 這個值代表G1產生RS報告的週期,設定為0就禁用。產生RS報告是非常耗時的操作,所以只有必要的時候才可以設定一個相對合理的比較高的值。可以用gc+remset=trace來檢視. |
-XX:GCTimeRatio=12 | 它是花在垃圾收集的時間和花在應用上的時間的比率的分母。實際花在垃圾收集的時間的計算方式是 1 / (1 + GCTimeRatio)。預設會有8%的時間花在垃圾收集上。 |