天天看點

JVM GC調優一則--增大Eden Space提高性能

緣起

線上有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參數:

-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參數改為:

-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 模式