觸發JVM進行Full GC的情況及應對策略

業餘草發表於2019-03-19

很多人都分不清 Major GC, Full GC 的概念,事實上我查了下資料,也沒有查到非常精確的Major GC和Full GC的概念定義。分不清這兩個概念可能就會對這個問題疑惑:Full GC會引起Minor GC嗎?

經過一系列的查詢和對JVM表現的分析,基本可以給Full GC 下一個定義了,這篇說一說概念和理由。

堆記憶體劃分為 Eden、Survivor 和 Tenured/Old 空間,如下圖所示:

從年輕代空間(包括 Eden 和 Survivor 區域)回收記憶體被稱為 Minor GC,對老年代GC稱為Major GC,而Full GC是對整個堆來說的,在最近幾個版本的JDK裡預設包括了對永生帶即方法區的回收(JDK8中無永生帶了),出現Full GC的時候經常伴隨至少一次的Minor GC,但非絕對的。Major GC的速度一般會比Minor GC慢10倍以上。下邊看看有那種情況觸發JVM進行Full GC及應對策略。

 

1、System.gc()方法的呼叫

 

此方法的呼叫是建議JVM進行Full GC,雖然只是建議而非一定,但很多情況下它會觸發 Full GC,從而增加Full GC的頻率,也即增加了間歇性停頓的次數。強烈影響系建議能不使用此方法就別使用,讓虛擬機器自己去管理它的記憶體,可通過通過-XX:+ DisableExplicitGC來禁止RMI呼叫System.gc。


2、老年代代空間不足

老年代空間只有在新生代物件轉入及建立為大物件、大陣列時才會出現不足的現象,當執行Full GC後空間仍然不足,則丟擲如下錯誤:
java.lang.OutOfMemoryError: Java heap space 
為避免以上兩種狀況引起的Full GC,調優時應儘量做到讓物件在Minor GC階段被回收、讓物件在新生代多存活一段時間及不要建立過大的物件及陣列。

3、永生區空間不足

JVM規範中執行時資料區域中的方法區,在HotSpot虛擬機器中又被習慣稱為永生代或者永生區,Permanet Generation中存放的為一些class的資訊、常量、靜態變數等資料,當系統中要載入的類、反射的類和呼叫的方法較多時,Permanet Generation可能會被佔滿,在未配置為採用CMS GC的情況下也會執行Full GC。如果經過Full GC仍然回收不了,那麼JVM會丟擲如下錯誤資訊:
java.lang.OutOfMemoryError: PermGen space 
為避免Perm Gen佔滿造成Full GC現象,可採用的方法為增大Perm Gen空間或轉為使用CMS GC。


4、CMS GC時出現promotion failed和concurrent mode failure

對於採用CMS進行老年代GC的程式而言,尤其要注意GC日誌中是否有promotion failed和concurrent mode failure兩種狀況,當這兩種狀況出現時可能

會觸發Full GC。
promotion failed是在進行Minor GC時,survivor space放不下、物件只能放入老年代,而此時老年代也放不下造成的;concurrent mode failure是在

執行CMS GC的過程中同時有物件要放入老年代,而此時老年代空間不足造成的(有時候“空間不足”是CMS GC時當前的浮動垃圾過多導致暫時性的空間不足觸發Full GC)。
對措施為:增大survivor space、老年代空間或調低觸發併發GC的比率,但在JDK 5.0+、6.0+的版本中有可能會由於JDK的bug29導致CMS在remark完畢

後很久才觸發sweeping動作。對於這種狀況,可通過設定-XX: CMSMaxAbortablePrecleanTime=5(單位為ms)來避免。


5、統計得到的Minor GC晉升到舊生代的平均大小大於老年代的剩餘空間

這是一個較為複雜的觸發情況,Hotspot為了避免由於新生代物件晉升到舊生代導致舊生代空間不足的現象,在進行Minor GC時,做了一個判斷,如果之

前統計所得到的Minor GC晉升到舊生代的平均大小大於舊生代的剩餘空間,那麼就直接觸發Full GC。
例如程式第一次觸發Minor GC後,有6MB的物件晉升到舊生代,那麼當下一次Minor GC發生時,首先檢查舊生代的剩餘空間是否大於6MB,如果小於6MB,

則執行Full GC。
當新生代採用PS GC時,方式稍有不同,PS GC是在Minor GC後也會檢查,例如上面的例子中第一次Minor GC後,PS GC會檢查此時舊生代的剩餘空間是否

大於6MB,如小於,則觸發對舊生代的回收。
除了以上4種狀況外,對於使用RMI來進行RPC或管理的Sun JDK應用而言,預設情況下會一小時執行一次Full GC。可通過在啟動時通過- java -

Dsun.rmi.dgc.client.gcInterval=3600000來設定Full GC執行的間隔時間或通過-XX:+ DisableExplicitGC來禁止RMI呼叫System.gc。

 

6、堆中分配很大的物件
 

所謂大物件,是指需要大量連續記憶體空間的java物件,例如很長的陣列,此種物件會直接進入老年代,而老年代雖然有很大的剩餘空間,但是無法找到足夠大的連續空間來分配給當前物件,此種情況就會觸發JVM進行Full GC。

為了解決這個問題,CMS垃圾收集器提供了一個可配置的引數,即-XX:+UseCMSCompactAtFullCollection開關引數,用於在“享受”完Full GC服務之後額外免費贈送一個碎片整理的過程,記憶體整理的過程無法併發的,空間碎片問題沒有了,但提頓時間不得不變長了,JVM設計者們還提供了另外一個引數 -XX:CMSFullGCsBeforeCompaction,這個引數用於設定在執行多少次不壓縮的Full GC後,跟著來一次帶壓縮的。

相關文章