線上有Tomcat升級到7.0.52版,而後有應用的JVM FullGC變頻繁,在高峯期socket鏈接數,Cpu使用率都暴增。java
思路是Tomcat自己的代碼應該是沒有問題的,有問題的多是應用代碼升級,或者環境改變了,總之Tomcat的優先級排在最後。算法
先把應用的heap dump下來分析下:apache
jmap -dump:format=b,file=path pidless
用IBM的Heap Analyser分析,發現dubbo rpc調用的RpcInvocation對象和taglibs的SimpleForEachIterator對象佔用了很大部份內存。socket
正常來講,這兩種類型的對象都應該能夠很快被回收掉,怎麼會佔用了那麼大的內存空間?是否是有別的對象引用了它們,致使不能釋放?工具
再仔細分析,發現RpcInvocation對象都是root refer的,也就是根對象,正常來講根對象應該能夠很快就被回收掉的,爲何在內存中會有那麼多對象?性能
再查看應用的JVM參數:spa
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內存。.net
顯然在新生代裏,沒辦法放下那麼多的對象,這些對象必然是被放到老生代(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,咱們瞭解到這些信息就足夠了。
大部分新對象在Eden Space上分配,當Eden Space滿了,則要用到Survivor Space來回收。YGC的算法是很快的。
屢次YGC以後,還存活的對象就會被移到Old Generation(old space)上,當Old Generation滿了的時候,就會FGC,FGC有一般比較慢。
Permanent Space只要你在開始時分配了足夠大的空間,那它能夠不用管。
咱們能夠得出一些結論:
總結下來,能夠發現,理想的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 模式