JVM GC調優一則–增大Eden Space提高效能

五柳-先生發表於2016-04-21

緣起

線上有Tomcat升級到7.0.52版,然後有應用的JVM FullGC變頻繁,在高峰期socket連線數,Cpu使用率都暴增。

思路

思路是Tomcat本身的程式碼應該是沒有問題的,有問題的可能是應用程式碼升級,或者環境改變了,總之Tomcat的優先順序排在最後。

先把應用的heap dump下來分析下:

jmap -dump:format=b,file=path pid

用IBM的Heap Analyser分析,發現dubbo rpc呼叫的RpcInvocation物件和taglibs的SimpleForEachIterator物件佔用了很大部分記憶體。

正常來說,這兩種型別的物件都應該可以很快被回收掉,怎麼會佔用了那麼大的記憶體空間?是不是有別的物件引用了它們,導致不能釋放?

再仔細分析,發現RpcInvocation物件都是root refer的,也就是根物件,正常來說根物件應該可以很快就被回收掉的,為什麼在記憶體中會有那麼多物件?

再檢視應用的JVM引數:

1
-Xms2g -Xmx2g -Xmn256m -XX:SurvivorRatio=8 -XX:ParallelGCThreads=8 -XX:PermSize=512m -XX:MaxPermSize=512m -Xss256k -XX:-DisableExplicitGC -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled

首先發現應用的新生代,即-Xmn256m 設定得太小了。對照上面RpcInvocation物件佔用了226M,SimpleForEachIterator佔用了267M記憶體。

顯然在新生代裡,沒辦法放下那麼多的物件,這些物件必然是被放到老生代(old space)裡去了。

既然RpcInvocation物件和SimpleForEachIterator物件應該都是可以很快被回收了,那麼思路變成,觸發一下線上的FullGC,看下物件有沒有被回收。

在觸發之前,先用jmap -histo pid統計下物件的數量:
34:        136762        4376384  com.alibaba.dubbo.rpc.RpcInvocation
129:         16345         392280  org.apache.taglibs.standard.tag.common.core.ForEachSupport$SimpleForEachIterator
用 jmap -histo:live <pid> 觸發Full GC之後:
294:           625          20000  com.alibaba.dubbo.rpc.RpcInvocation
495:           292           7008  org.apache.taglibs.standard.tag.common.core.ForEachSupport$SimpleForEachIterator
果然數量大大的減少了。

所以結論比較明顯了,新生代(Young generation)的空間太小,導致有一些本應該可以很快就被回收的物件被放到了老生代(Old generation)裡,導致老生代上漲很快,頻繁Full GC。

於是想辦法增加新生代的大小,把JVM引數改為:

1
-Xms2g -Xmx2g -XX:ParallelGCThreads=8 -XX:PermSize=256m -XX:MaxPermSize=512m -Xss256k -XX:-DisableExplicitGC -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled

因為觀察到PermSize實際上只用了不到200M,沒有必要設定為512M,浪費記憶體,所以改為 -XX:PermSize=256m -XX:MaxPermSize=512m 。

另外,把新生代最大限制-Xmn256m 去掉。因為預設的NewRatio = 2,即除了PermSize,新生代大約佔記憶體的1/3,即約(2048 – 256) /3 = 597M。和原來相比增大了一倍不止。

修改上線之後,觀察發現Old Space增長緩慢,FullGC次數大大減少,時間在50ms下,Yong GC都在10ms下,達到了想要的效果。

簡單的GC過程分析

首先來看一張GC的模型圖,很形象:

簡單來說,對於GC,我們瞭解到這些資訊就足夠了。

大部分新物件在Eden Space上分配,當Eden Space滿了,則要用到Survivor Space來回收。YGC的演算法是很快的。

多次YGC之後,還存活的物件就會被移到Old Generation(old space)上,當Old Generation滿了的時候,就會FGC,FGC有通常比較慢。

Permanent Space只要你在開始時分配了足夠大的空間,那它可以不用管。

我們可以得出一些結論:

  • 合理減少物件進入老生代;
  • Old Space可能會一直增長,有時沒有辦法避免不讓物件進入Old Space,當然也有一些程式是從來都不執行FGC的;
  • 是不是盡全力防止物件進入老生代?顯然不是,有些物件如果長久存在在新生代裡,顯然加重了YGC的負擔,多次YGC之後仍然存活的物件顯然應該放到Old Space裡。

理想的GC/記憶體使用情況

總結下來,可以發現,理想的GC情況應該是這樣的:

Old Space增長緩慢,FullGC次數少,FullGC的時間短(大部情況應該要在1秒內)。

總結:

儘量少加上一些預設引數。這點我很贊同RednaxelaFX的看法,配置了預設引數除了讓後面調優的人蛋疼之外,沒有太多的幫助。

GC調優就是一個取捨權衡的過程,有得必有失,最好可以在多個不同的例項裡,配置不同的引數,然後進行比較。

有很多命令列工具或者圖形工具可以使用,好的工具事半功倍。

參考:

http://www.alphaworks.ibm.com/tech/heapanalyzer‎    IBM Heap Analyser

http://hllvm.group.iteye.com/group/topic/27945    JVM調優的”標準引數”的各種陷阱,RednaxelaFX 出品,強列推薦

http://www.taobaotesting.com/blogs/2392      Java效能剖析1——JVM記憶體管理與垃圾回收

http://www.oschina.net/translate/using-headless-mode-in-java-se      在 Java SE 平臺上使用 Headless 模式

轉載:http://www.importnew.com/19308.html?utm_source=tuicool&utm_medium=referral

相關文章