1. 程式人生 > >JVM每小時執行一次FULL GC問題

JVM每小時執行一次FULL GC問題

引子

最近分析系統中部分機器記憶體使用率偏高報警問題,發現這部分機器堆記憶體使用率持續增長,當堆記憶體接近上限的時候才會觸發一次FULL GC;其餘機器記憶體使用率整體波動比較穩定,且FULL GC頻率大致是1個小時。

針對這個報警問題,考慮將這部分機器的JVM FULL GC也調整成1個小時執行一次。開始增加JVM引數-XX:+UnlockExperimentalVMOptions,沒有起作用。後來才發現這個是由tomcat防止記憶體洩露的監聽器JreMemoryLeakPreventionListener(server.xml中配置)觸發的,將tomcat版本由6.0.44降低到了6.0.33就OK了。

Tomcat防止記憶體洩露監聽器

tomcat為了防止記憶體洩露,會註冊一個監聽器,週期性的觸發System.gc(),下面是server.xml中監聽器的配置

<!-- Prevent memory leaks due to use of particular java/javax APIs-->
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />

tomcat 7提出了每隔一小時執行一次FULL GC這個bugTomcat 7.0.28版本中將執行頻率由一小時修改成 Long.MAX_VALUE-1。並在Tomcat 6.0.36

版本中同步修復了這個bug。

這個bug的修復可以通過下面兩個版本修復前和修復後的對比直觀的看出來:

下面是Tomcat 6.0.33版本中,JreMemoryLeakPreventionListener觸發FULL GC的實現(程式碼為反編譯),如下:

Class clazz = Class.forName("sun.misc.GC");
Method method = clazz.getDeclaredMethod("requestLatency", new Class[] { Long.TYPE });
method.invoke(null, new Object[] { Long.valueOf(3600000
L) });

再看Tomcat 6.0.44版本中,修復後的實現:

Class clazz = Class.forName("sun.misc.GC");
Method method = clazz.getDeclaredMethod("requestLatency", new Class[] { Long.TYPE });
method.invoke(null, new Object[] { Long.valueOf(9223372036854775806L) });

jdk sun.misc.GC類的requestLatency方法,傳入的引數表示一次gc請求的延遲時間。會有個低優先順序的daemon執行緒執行呼叫System.gc()

防止Tomcat每小時FULL GC

在tomcat沒修復此bug之前,可以通過如下方式防止每小時FULL GC:(可閱讀原文

  • 增加JVM引數-XX:+DisableExplicitGC,這個引數會使顯示的呼叫System.gc()空轉,不會執行垃圾回收
  • 不增加JVM引數-XX:+DisableExplicitGC,換成增加-XX:+ExplicitGCInvokesConcurrent,使FULL GC使用併發垃圾回收器CMS,提高回收效率(CMS併發GC,stop-the-world時間較短)
  • 修改server.xml配置,gcDaemonProtection引數改為false,預設是true
 <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener"
gcDaemonProtection="false"/>
  • 修改server.xml配置,去掉JreMemoryLeakPreventionListener監聽器

對於現在,tomcat已經修復了此bug,升級tomcat版本也可以解決每小時執行一次FULL GC問題

Tomcat每小時GC問題

在網上查這個問題時候,很多人都把每小時執行一次GC作為bug,尋找解決方案(我是反其道而行之,把系統調整成每小時GC一次 -_-)。

每小時一次FULL GC到底有何問題?
我目前是這麼理解的:FULL GC會導致stop-the-world,頻繁的FULL GC會影響系統的可用性。
stackoverflow上有個提問是這麼描述這個問題的:當伺服器叢集批量啟動後,執行FULL GC的頻率和時間點大致相同,若FULL GC時間過長,會讓負載均衡器覺得各個機器服務不可用,可能導致整個服務下線。

但是從FULL GC的角度考慮,對於調整的這個業務系統,目前沒有發現FULL GC的stop-the-world對系統有嚴重影響(相對來說,實時系統對GC的低停頓、高吞吐量要求更高),所以我還是把系統改成了每小時執行一次FULL GC的模式。
這個問題有待以後深入學習、理解 -_-

擴充套件:jstat分析GC

jdk提供了工具jstat分析系統的GC行為,可以分析minor gc和full gc發生的時間和GC時間
jstat -gcutil javaPid outputInterval
jstat -gccause javaPid outputInterval