最近剛剛將本身的一個應用從CMS升級到G1,在一天早上,剛剛到辦公室坐下,就收到手機一陣報警,去查看了監控,發現機器的內存出現了一個90度的漲幅,以下圖所示:java
在查看GC日誌後,發現那個時間點附近出現了「to-space exhausted」這種日誌(關於G1的日誌學習,參見我以前的文章:【譯】深刻理解G1的GC日誌(一)))面試
在這裏,我比較奇怪的是爲啥to-sapce exhausted會致使整個機器的內存激增。咱們JVM團隊同窗給個人解釋是:老區不夠了,這個時候會把young區全部對象無論死活都轉成old區對象,因此總的內存使用量會暴增。這一個知識點,我以前學習G1的時候還真沒有get到(關於G1的基本知識,參見以前的文章:多是最全面的G1學習筆記))。後端
不過,我有另一個疑問:xmx和xms相同的話堆空間應該不變,一開始就分配5g,而後加上非堆內存,那麼java進程起來後就會超過5g,這是沒問題的;可是這裏利用空閒的內存也應該是利用堆上的空間,而後總體的內存塊應該已經分配出去了,應該不會出現機器內存激增的狀況。JVM團隊的同窗給我解釋道:沒有,第一次讀寫到了纔會實際從os分配出來物理內存。併發
針對上面的問題,咱們最終肯定了下面的調優建議:性能
基於上面這個問題,我又去找了一些資料,整理以下。學習
在這本書的123頁有提到,上面這種狀況屬於晉升失敗的狀況——G1收集器完成了標記階段,開始啓動混合式垃圾收集,準備要清理老年代分區,可是老年代分區在垃圾收集器釋放出足夠的空間以前就已經被耗盡了。這種失敗一般意味着混合式垃圾收集須要更迅速得完成垃圾收集,每次新生代垃圾收集須要處理更多的老年代分區。通常來講,一系列的to-space exhausted以後會跟着一次FGC。優化
在咱們上面的這個例子中,是old區的使用速度超過了垃圾收集器的回收速度,所以能夠考慮兩種調優的思路spa
-XX:InitiatingHeapOccupancyPercent=N
這個參數,默認狀況下該參數是45(PS:這個參數表示的是佔用整個堆內存的比例),不過,這個參數也不能調得過小,不然會致使過多的併發收集週期和混合式垃圾收集,給應用早成過多的停頓。 -XX:G1MixedGCCountTarget=N
參數能夠控制每一個混合式週期中回收的Old分區數量,該參數的默認值是8; 要特別關注日誌片斷中的"to-space exhausted"和「Evacuation Failure」兩個日誌,以下圖所示。能夠看出,Evacuation Failure消耗了684.1ms,也就是說,此次轉移失敗致使了將近1s的應用暫停。線程
這種狀況屬於轉移失敗,這本書給出了兩點建議:3d
-XX:InitiatingHeapOccupancyPercent=N
這個參數的值,由於轉移失敗的代價比多執行一些併發標記週期高不少 -XX:ConcGCThreads
,增長用於垃圾收集的線程個數,代價是會多一些CPU的消耗;也就是會佔用Java應用的CPU時間,這一點也須要權衡一下。 -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面試題、我的成長和自我管理等主題,爲讀者提供一線開發者的工做和成長經驗,期待你能在這裏有所收穫。