1. 程式人生 > 程式設計 >JVM調優實戰:G1中的to-space exhausted問題

JVM調優實戰:G1中的to-space exhausted問題

最近剛剛將自己的一個應用從CMS升級到G1,在一天早上,剛剛到辦公室坐下,就收到手機一陣報警,去查看了監控,發現機器的記憶體出現了一個90度的漲幅,如下圖所示:

image.png

在檢視GC日誌後,發現那個時間點附近出現了“to-space exhausted”這種日誌(關於G1的日誌學習,參見我之前的文章:【譯】深入理解G1的GC日誌(一)))

image.png

在這裡,我比較奇怪的是為啥to-sapce exhausted會導致整個機器的記憶體激增。我們JVM團隊同學給我的解釋是:老區不夠了,這個時候會把young區所有物件不管死活都轉成old區物件,所以總的記憶體使用量會暴增。這一個知識點,我之前學習G1的時候還真沒有get到(關於G1的基本知識,參見之前的文章:

可能是最全面的G1學習筆記))。

不過,我有另外一個疑問:xmx和xms相同的話堆空間應該不變,一開始就分配5g,然後加上非堆記憶體,那麼java程式起來後就會超過5g,這是沒問題的;但是這裡利用空閒的記憶體也應該是利用堆上的空間,然後整體的記憶體塊應該已經分配出去了,應該不會出現機器記憶體激增的情況。JVM團隊的同學給我解釋道:沒有,第一次讀寫到了才會實際從os分配出來實體記憶體。

針對上面的問題,我們最終確定了下面的調優建議:

  • 這次沒有發生FGC,可能是由於我前面將xmx和xms調大了導致的,這次準備將xmx和xms先調回到原來的值;
  • 加上HeapDumpAfterFullGC引數,下次再發生類似情況的時候,就會觸發FGC,然後自動dump堆記憶體,就可以針對堆記憶體進行分析,看看是什麼物件佔用了這麼多記憶體,然後就可以針對性優化。

基於上面這個問題,我又去找了一些資料,整理如下。

《Java效能權威指南》

在這本書的123頁有提到,上面這種情況屬於晉升失敗的情況——G1收集器完成了標記階段,開始啟動混合式垃圾收集,準備要清理老年代分割槽,但是老年代分割槽在垃圾收集器釋放出足夠的空間之前就已經被耗盡了。這種失敗通常意味著混合式垃圾收集需要更迅速得完成垃圾收集,每次新生代垃圾收集需要處理更多的老年代分割槽。一般來說,一系列的to-space exhausted之後會跟著一次FGC。

在我們上面的這個例子中,是old區的使用速度超過了垃圾收集器的回收速度,因此可以考慮兩種調優的思路

  • 讓G1更早得啟動混合式垃圾收集週期,通過調小-XX:InitiatingHeapOccupancyPercent=N
    這個引數,預設情況下該引數是45(PS:這個引數表示的是佔用整個堆記憶體的比例),不過,這個引數也不能調得太小,否則會導致過多的併發收集週期和混合式垃圾收集,給應用早成過多的停頓。
  • 除了考慮增加速度,還可以考慮增加每次混合式垃圾收集收集的Old分割槽數量,通過調整-XX:G1MixedGCCountTarget=N引數可以控制每個混合式週期中回收的Old分割槽數量,該引數的預設值是8;

《Java效能調優指南》

要特別關注日誌片段中的"to-space exhausted"和“Evacuation Failure”兩個日誌,如下圖所示。可以看出,Evacuation Failure消耗了684.1ms,也就是說,這次轉移失敗導致了將近1s的應用暫停。

image.png

這種情況屬於轉移失敗,這本書給出了兩點建議:

  • 和《Java效能權威指南》一樣,也建議調小-XX:InitiatingHeapOccupancyPercent=N這個引數的值,因為轉移失敗的代價比多執行一些併發標記週期高很多
  • 建議通過調整-XX:ConcGCThreads,增加用於垃圾收集的執行緒個數,代價是會多一些CPU的消耗;也就是會佔用Java應用的CPU時間,這一點也需要權衡一下。
  • 有時候轉移失敗是由於survivor分割槽中沒有足夠的空間容納新晉升的物件,如果是這種情況,還可以考慮增加-XX:G1ReservePercent的大小,在G1中這個預設值是10%。

總結

JVM引數的調優,是一個不斷推導和嘗試的過程,其中最重要的資料就是GC日誌和Java堆記憶體快照,因此:(1)在JVM引數中一定要設定HeapDumpAfterFullGC和HeapDumpOnOutOfMemoryError兩個引數,可以在傳送FGC和OOM的時候將當時的Java堆情況記錄下來,用於事後分析;(2)GC日誌要單獨列印到一個日誌檔案中,方便分析,如果不特別設定,GC日誌會列印到stdout.log中,會有其他的日誌混合在中間,影響問題排查。

JVM的引數調優並不是萬能的,發生OOM或者FGC的時候,業務程式碼中也一定有不合理的地方,需要做合理的限制和優化,不能將所有的事情都交給JVM抗。***本號專注於後端技術、JVM問題排查和優化、Java面試題、個人成長和自我管理等主題,為讀者提供一線開發者的工作和成長經驗,期待你能在這裡有所收穫。javaadu