閱讀449 返回首頁    go 阿裏雲 go 技術社區[雲棲]


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調優就是一個取舍權衡的過程,有得必有失,最好可以在多個不同的實例裏,配置不同的參數,然後進行比較。

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

參考:

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

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

https://www.taobaotesting.com/blogs/2392      JAVA性能剖析1——JVM內存管理與垃圾回收

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

最後更新:2017-04-03 12:56:30

  上一篇:go JS操作剪貼板
  下一篇:go Oracle中的記錄(Record)