說明:以下大部分文字均摘自網絡上的某些“童鞋”的優秀文章。
一、對于GC的性能其實主要考慮以下兩個方面:
1、吞吐率throughput【工作時間(不包括GC的時間)占總運行的時間比】
2、暫停pause(GC發生時應用程序無法響應用戶的請求)
二、對于GC的性能可以從以下方面考慮:
1、整個堆空間
對于Server端的應用程序,有以下最佳實踐:
1)對于JVM分配盡可能多的內存空間。
2)固定堆空間的大小,將Xms和Xmx設為一樣的值。如果讓JVM自行控制堆空間大小的話,虛擬機啟動時分配的堆空間比較小,如果在程序運行過程中還需要初始化很多對象,虛擬
機就必須重復地增加內存,造成GC頻率的增加。
3)橫向增加服務器的數量,為程序服務的JVM內存總量也隨著增大。
2、新生代
從整體上看,新生代越大,minor GC就會越少。但由于我們一般是固定的堆內存空間,因此更大的新生代也就意味著更小的老生代,更小的老生代會帶來更多的Full GC(Full GC會伴隨
有minor GC)。
參數NewRatio反映的是新生代和老生代的大小比例。NewSize和MaxNewSize反映的是新生代空間的下限和上限,將這兩個值設為一樣就固定了新生代的大小(或者直接通過指定
Xms、Xmx和Xmn的大小來固定新生代的大小)。SurvivorRatio可以指定survivor區的大小,SurvivorRatio是eden區和survior區的大小比例。
一般而言,server端的app會有以下最佳實踐:
1)首先固定heap空間的大小,然后設定最佳的新生代空間的大小;
2)如果堆空間固定后,增加新生代的大小就意味著減小老生代的大小。因此在調節時應特別留意,讓老生代至少能夠保留10%-20%的空余空間,并能夠容納所有live的對象。
三、最佳實踐:
1)年輕代大小的選擇
響應時間優先的應用:盡可能增大新生代的大小,直到接近系統的最低響應時間限制(根據實際情況選擇)。在此種情況下,新生代收集發生的頻率也是最小的。同時,減少到達年老代
的對象,從而減少Full GC的發生幾率。
吞吐量優先的應用:盡可能增大新生代的大小,可能到達Gbit的程度。因為對響應時間沒有要求,垃圾收集可以并行進行,一般適合8CPU以上的應用系統。
避免設置過小:當新生代設置過小時會導致:1、minor GC的次數更加頻繁 2、可能導致minor GC對象直接進入老生代,如果此時老生代滿了,會觸發Full GC.
2)年老代大小選擇
響應時間優先的應用:老生代使用并發收集器(CMS GC),所以其大小需要小心設置,一般要考慮并發會話率和會話持續時間等一些參數。如果堆設置小了,可以會形成內存碎片,高
回收頻率以及應用暫停。而使用傳統的標記清除方式,如果堆設置大了,則需要較長的收集時間。最優化的方案,一般需要參考以下數據獲得:并發垃圾收集信息、永久代并發收集次數、傳
統GC信息、花在新生代和老生代回收上的時間比例。
吞吐量優先的應用:一般吞吐量優先的應用都有一個很大的新生代和一個較小的老生代。原因是這樣可以盡可能回收掉大部分短期對象,減少中期的對象,而老生代盡存放長期存活對象。
3)其他
較小堆引起的碎片問題:因為老生代的并發收集器使用標記清除算法,所以不會對堆進行壓縮。當收集器回收時,它會把相鄰的空間進行合并,這樣可以分配給較大的對象。但是,當
堆空間較小時,運行一段時間以后,就會出現"碎片",如果并發收集器找不到足夠的空間,那么并發收集器將會停止,然后使用傳統的標記清除方式進行回收。如果出現"碎片",可能需要
進行如下配置:
-XX:+UseCMSCompactAtFullCollection:使用并發收集器時,開啟對年老代的壓縮。
-XX:CMSFullGCsBeforeCompaction=0:上面配置開啟的情況下,這里設置多少次Full GC后,對年老代進行壓縮。
用64位操作系統。Linux下64位的jdk比32位jdk要慢一些,但是吃得內存更多,吞吐量更大。
XMX和XMS設置一樣大,MaxPermSize和MinPermSize設置一樣大,這樣可以減輕伸縮堆大小帶來的壓力 。
使用CMS GC的好處是用盡量少的新生代,經驗值是128M-256M, 然后老生代利用CMS并行收集, 這樣能保證系統低延遲的吞吐效率。 實際上CMS GC的收集停頓時間非常的短,
2G的內存大約20-80ms的應用程序停頓時間。
減少程序停頓時間:系統停頓的時間可能是GC的問題也可能是程序的問題,多用jmap和jstack查看或者killall -3 java,然后查看java控制臺日志,能看出很多問題。有一次,網站突然
很慢,利用jstack一看,原來是自己寫的URLConnection連接太多沒有釋放造成的。
程序應用緩存的問題:如果程序應用了緩存,那么老生代應該設置的大一些,緩存的HashMap不應該無限制增長,建議采用LRU算法的Map做緩存,LRU Map(例如Jakarta
Commons中提供的org.apache.commons.collections.map.LRUMap)的最大長度也要根據實際情況設定。
采用并發回收時,新生代小一點,老生代要大,因為老生代用的是并發回收,即使時間長點也不會影響其他程序繼續運行,網站不會停頓。
JVM 參數的設置(特別是 –Xmx –Xms –Xmn -XX:SurvivorRatio -XX:MaxTenuringThreshold等參數的設置沒有一個固定的公式,需要根據PV、老生代實際數據、新生代GC次數等
多方面來衡量。為了避免promotion faild,可能會導致xmn設置偏小,也意味著新生代GC的次數會增多,處理并發訪問的能力下降等問題。每個參數的調整都需要經過詳細的性能測試,
才能找到特定應用的最佳配置。
打印GC日志:調試的時候設置一些打印參數,如-XX:+PrintClassHistogram -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -Xloggc:log/gc.log,這樣可
以從gc.log里看出一些問題出來。
4)promotion failed(晉升失敗):第一個原因是擔保空間不夠,擔保空間里的對象還不應該被移動到老生代,但新生代又有很多對象需要放入擔保空間;第二個原因是老生代沒有足夠的空間接納來自新生代的對象;這兩種情況都會轉向Full GC,網站停頓時間較長。
解決方方案一:
第一個原因我的最終解決辦法是去掉擔保空間,設置-XX:SurvivorRatio=65536 -XX:MaxTenuringThreshold=0即可,第二個原因我的解決辦法是設置
CMSInitiatingOccupancyFraction為某個值(假設70),這樣老生代空間到70%時就開始執行CMS,老生代有足夠的空間接納來自新生代的對象。
方案一的改進方案:
方案一中沒有用到擔保空間,所以老生代容易滿,CMS執行會比較頻繁。我改善了一下,還是用擔保空間,但是把擔保空間加大,這樣也不會有promotion failed。具體操作上,32位Linux和64位Linux好像不一樣,64位系統似乎只要配置MaxTenuringThreshold參數,CMS還是有暫停。為了解決暫停問題和promotion failed問題,最后我設置-
XX:SurvivorRatio=1 ,并把MaxTenuringThreshold去掉,這樣即沒有暫停又不會有promotoin failed,而且更重要的是,老生代和永久代上升非常慢(因為好多對象到不了年老代就
被回收了),所以CMS執行頻率非常低,好幾個小時才執行一次,這樣,服務器都不用重啟了。
-Xmx4000M -Xms4000M -Xmn600M -XX:PermSize=500M -XX:MaxPermSize=500M -Xss256K -XX:+DisableExplicitGC -XX:SurvivorRatio=1
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0
-XX:+CMSClassUnloadingEnabled -XX:LargePageSizeInBytes=128M -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSInitiatingOccupancyFraction=80 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+PrintClassHistogram -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
-XX:+PrintHeapAtGC -Xloggc:log/gc.log
5)CMSInitiatingOccupancyFraction值與Xmn的關系公式
上面介紹了promontion faild產生的原因是Eden空間不足的情況下將Eden與From survivor中的存活對象存入To survivor區時,To survivor區的空間不足,再次晉升到old gen區,
而old gen區內存也不夠的情況下產生了promontion faild從而導致full gc。那可以推斷出:eden+from survivor < old gen區剩余內存時,不會出現promontion faild的情況,即:
(Xmx-Xmn)*(1-CMSInitiatingOccupancyFraction/100)>=[Xmn-Xmn/(SurvivorRatior+2)] 進而推斷出:
CMSInitiatingOccupancyFraction <={(Xmx-Xmn)-[Xmn-Xmn/(SurvivorRatior+2)]}/(Xmx-Xmn)*100
例如:
當Xmx=128 Xmn=36 SurvivorRatior=1時 CMSInitiatingOccupancyFraction<=((128.0-36)-(36-36/(1+2)))/(128-36)*100 =73.913
當Xmx=128 Xmn=24 SurvivorRatior=1時 CMSInitiatingOccupancyFraction<=((128.0-24)-(24-24/(1+2)))/(128-24)*100=84.615…
當Xmx=3000 Xmn=600 SurvivorRatior=1時 CMSInitiatingOccupancyFraction<=((3000.0-600)-(600-600/(1+2)))/(3000-600)*100=83.33
當CMSInitiatingOccupancyFraction低于70% 需要調整Xmn或SurvivorRatior值。
四、內存泄露的分析
HPjmeter是一個GC的圖形化分析工具,由上圖可以看出GC始終無法回收heap內存空間,以使heap內存空間的使用量持續升高,明顯存在內存泄露的可能性。定位內存泄露,可以生
成dump文件,并利用MAT進行分析,查找原因。
內存分析工具:
詳細信息可參考文章http://qa.taobao.com/?p=14264。
JVM內存狀況查看方案及工具:http://www.51testing.com/html/58/n-237858.html
使用 Eclipse Memory Analyzer 進行堆轉儲文件分析:http://www.ibm.com/developerworks/cn/opensource/os-cn-ecl-ma/index.html?ca=drs-
參考文獻:
1、JVM系列三:JVM參數設置、分析 http://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html
2、一個典型的OutOfMemory分析過程 http://hbase.iteye.com/blog/1356450
3、使用MAT分析內存泄露 http://qa.taobao.com/?p=14264
4、JVM內存狀況查看方法和分析工具 http://www.51testing.com/html/58/n-237858.html
5、使用 Eclipse Memory Analyzer 進行堆轉儲文件分析:http://www.ibm.com/developerworks/cn/opensource/os-cn-ecl-ma/index.html?ca=drs-