說(shuō)明:以下大部分文字均摘自網(wǎng)絡(luò)上的某些“童鞋”的優(yōu)秀文章。

一、對(duì)于GC的性能其實(shí)主要考慮以下兩個(gè)方面:

     1、吞吐率throughput【工作時(shí)間(不包括GC的時(shí)間)占總運(yùn)行的時(shí)間比】

     2、暫停pause(GC發(fā)生時(shí)應(yīng)用程序無(wú)法響應(yīng)用戶(hù)的請(qǐng)求)

二、對(duì)于GC的性能可以從以下方面考慮:

1、整個(gè)堆空間

對(duì)于Server端的應(yīng)用程序,有以下最佳實(shí)踐:

     1)對(duì)于JVM分配盡可能多的內(nèi)存空間。

     2)固定堆空間的大小,將Xms和Xmx設(shè)為一樣的值。如果讓JVM自行控制堆空間大小的話(huà),虛擬機(jī)啟動(dòng)時(shí)分配的堆空間比較小,如果在程序運(yùn)行過(guò)程中還需要初始化很多對(duì)象,虛擬

機(jī)就必須重復(fù)地增加內(nèi)存,造成GC頻率的增加。

     3)橫向增加服務(wù)器的數(shù)量,為程序服務(wù)的JVM內(nèi)存總量也隨著增大。

2、新生代

      從整體上看,新生代越大,minor GC就會(huì)越少。但由于我們一般是固定的堆內(nèi)存空間,因此更大的新生代也就意味著更小的老生代,更小的老生代會(huì)帶來(lái)更多的Full GC(Full GC會(huì)伴隨

有minor GC)。

      參數(shù)NewRatio反映的是新生代和老生代的大小比例。NewSize和MaxNewSize反映的是新生代空間的下限和上限,將這兩個(gè)值設(shè)為一樣就固定了新生代的大小(或者直接通過(guò)指定

Xms、Xmx和Xmn的大小來(lái)固定新生代的大小)。SurvivorRatio可以指定survivor區(qū)的大小,SurvivorRatio是eden區(qū)和survior區(qū)的大小比例。

      一般而言,server端的app會(huì)有以下最佳實(shí)踐:

      1)首先固定heap空間的大小,然后設(shè)定最佳的新生代空間的大小;

      2)如果堆空間固定后,增加新生代的大小就意味著減小老生代的大小。因此在調(diào)節(jié)時(shí)應(yīng)特別留意,讓老生代至少能夠保留10%-20%的空余空間,并能夠容納所有l(wèi)ive的對(duì)象。

三、最佳實(shí)踐:

      1)年輕代大小的選擇

      響應(yīng)時(shí)間優(yōu)先的應(yīng)用:盡可能增大新生代的大小,直到接近系統(tǒng)的最低響應(yīng)時(shí)間限制(根據(jù)實(shí)際情況選擇)。在此種情況下,新生代收集發(fā)生的頻率也是最小的。同時(shí),減少到達(dá)年老代

的對(duì)象,從而減少Full GC的發(fā)生幾率。

      吞吐量?jī)?yōu)先的應(yīng)用:盡可能增大新生代的大小,可能到達(dá)Gbit的程度。因?yàn)閷?duì)響應(yīng)時(shí)間沒(méi)有要求,垃圾收集可以并行進(jìn)行,一般適合8CPU以上的應(yīng)用系統(tǒng)。

      避免設(shè)置過(guò)小:當(dāng)新生代設(shè)置過(guò)小時(shí)會(huì)導(dǎo)致:1、minor GC的次數(shù)更加頻繁    2、可能導(dǎo)致minor GC對(duì)象直接進(jìn)入老生代,如果此時(shí)老生代滿(mǎn)了,會(huì)觸發(fā)Full GC.

      2)年老代大小選擇

      響應(yīng)時(shí)間優(yōu)先的應(yīng)用:老生代使用并發(fā)收集器(CMS GC),所以其大小需要小心設(shè)置,一般要考慮并發(fā)會(huì)話(huà)率和會(huì)話(huà)持續(xù)時(shí)間等一些參數(shù)。如果堆設(shè)置小了,可以會(huì)形成內(nèi)存碎片,高

回收頻率以及應(yīng)用暫停。而使用傳統(tǒng)的標(biāo)記清除方式,如果堆設(shè)置大了,則需要較長(zhǎng)的收集時(shí)間。最優(yōu)化的方案,一般需要參考以下數(shù)據(jù)獲得:并發(fā)垃圾收集信息、永久代并發(fā)收集次數(shù)、傳

統(tǒng)GC信息、花在新生代和老生代回收上的時(shí)間比例。

      吞吐量?jī)?yōu)先的應(yīng)用:一般吞吐量?jī)?yōu)先的應(yīng)用都有一個(gè)很大的新生代和一個(gè)較小的老生代。原因是這樣可以盡可能回收掉大部分短期對(duì)象,減少中期的對(duì)象,而老生代盡存放長(zhǎng)期存活對(duì)象。

     3)其他

     較小堆引起的碎片問(wèn)題:因?yàn)槔仙牟l(fā)收集器使用標(biāo)記清除算法,所以不會(huì)對(duì)堆進(jìn)行壓縮。當(dāng)收集器回收時(shí),它會(huì)把相鄰的空間進(jìn)行合并,這樣可以分配給較大的對(duì)象。但是,當(dāng)

堆空間較小時(shí),運(yùn)行一段時(shí)間以后,就會(huì)出現(xiàn)"碎片",如果并發(fā)收集器找不到足夠的空間,那么并發(fā)收集器將會(huì)停止,然后使用傳統(tǒng)的標(biāo)記清除方式進(jìn)行回收。如果出現(xiàn)"碎片",可能需要

進(jìn)行如下配置:

     -XX:+UseCMSCompactAtFullCollection:使用并發(fā)收集器時(shí),開(kāi)啟對(duì)年老代的壓縮。

    -XX:CMSFullGCsBeforeCompaction=0:上面配置開(kāi)啟的情況下,這里設(shè)置多少次Full GC后,對(duì)年老代進(jìn)行壓縮。

     用64位操作系統(tǒng)。Linux下64位的jdk比32位jdk要慢一些,但是吃得內(nèi)存更多,吞吐量更大。

     XMX和XMS設(shè)置一樣大,MaxPermSize和MinPermSize設(shè)置一樣大,這樣可以減輕伸縮堆大小帶來(lái)的壓力 。

     使用CMS GC的好處是用盡量少的新生代,經(jīng)驗(yàn)值是128M-256M, 然后老生代利用CMS并行收集, 這樣能保證系統(tǒng)低延遲的吞吐效率。 實(shí)際上CMS GC的收集停頓時(shí)間非常的短,

2G的內(nèi)存大約20-80ms的應(yīng)用程序停頓時(shí)間。

     減少程序停頓時(shí)間:系統(tǒng)停頓的時(shí)間可能是GC的問(wèn)題也可能是程序的問(wèn)題,多用jmap和jstack查看或者killall -3 java,然后查看java控制臺(tái)日志,能看出很多問(wèn)題。有一次,網(wǎng)站突然

很慢,利用jstack一看,原來(lái)是自己寫(xiě)的URLConnection連接太多沒(méi)有釋放造成的。

     程序應(yīng)用緩存的問(wèn)題:如果程序應(yīng)用了緩存,那么老生代應(yīng)該設(shè)置的大一些,緩存的HashMap不應(yīng)該無(wú)限制增長(zhǎng),建議采用LRU算法的Map做緩存,LRU Map(例如Jakarta

Commons中提供的org.apache.commons.collections.map.LRUMap)的最大長(zhǎng)度也要根據(jù)實(shí)際情況設(shè)定。

     采用并發(fā)回收時(shí),新生代小一點(diǎn),老生代要大,因?yàn)槔仙玫氖遣l(fā)回收,即使時(shí)間長(zhǎng)點(diǎn)也不會(huì)影響其他程序繼續(xù)運(yùn)行,網(wǎng)站不會(huì)停頓。

     JVM 參數(shù)的設(shè)置(特別是 –Xmx –Xms –Xmn -XX:SurvivorRatio  -XX:MaxTenuringThreshold等參數(shù)的設(shè)置沒(méi)有一個(gè)固定的公式,需要根據(jù)PV、老生代實(shí)際數(shù)據(jù)、新生代GC次數(shù)等

多方面來(lái)衡量。為了避免promotion faild,可能會(huì)導(dǎo)致xmn設(shè)置偏小,也意味著新生代GC的次數(shù)會(huì)增多,處理并發(fā)訪(fǎng)問(wèn)的能力下降等問(wèn)題。每個(gè)參數(shù)的調(diào)整都需要經(jīng)過(guò)詳細(xì)的性能測(cè)試,

才能找到特定應(yīng)用的最佳配置。

     打印GC日志:調(diào)試的時(shí)候設(shè)置一些打印參數(shù),如-XX:+PrintClassHistogram -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -Xloggc:log/gc.log,這樣可

以從gc.log里看出一些問(wèn)題出來(lái)。

      4)promotion failed(晉升失敗):第一個(gè)原因是擔(dān)保空間不夠,擔(dān)保空間里的對(duì)象還不應(yīng)該被移動(dòng)到老生代,但新生代又有很多對(duì)象需要放入擔(dān)保空間;第二個(gè)原因是老生代沒(méi)有足夠的空間接納來(lái)自新生代的對(duì)象;這兩種情況都會(huì)轉(zhuǎn)向Full GC,網(wǎng)站停頓時(shí)間較長(zhǎng)。

     解決方方案一

     第一個(gè)原因我的最終解決辦法是去掉擔(dān)保空間,設(shè)置-XX:SurvivorRatio=65536 -XX:MaxTenuringThreshold=0即可,第二個(gè)原因我的解決辦法是設(shè)置

CMSInitiatingOccupancyFraction為某個(gè)值(假設(shè)70),這樣老生代空間到70%時(shí)就開(kāi)始執(zhí)行CMS,老生代有足夠的空間接納來(lái)自新生代的對(duì)象。

     方案一的改進(jìn)方案

     方案一中沒(méi)有用到擔(dān)保空間,所以老生代容易滿(mǎn),CMS執(zhí)行會(huì)比較頻繁。我改善了一下,還是用擔(dān)保空間,但是把擔(dān)保空間加大,這樣也不會(huì)有promotion failed。具體操作上,32位Linux和64位Linux好像不一樣,64位系統(tǒng)似乎只要配置MaxTenuringThreshold參數(shù),CMS還是有暫停。為了解決暫停問(wèn)題和promotion failed問(wèn)題,最后我設(shè)置-

XX:SurvivorRatio=1 ,并把MaxTenuringThreshold去掉,這樣即沒(méi)有暫停又不會(huì)有promotoin failed,而且更重要的是,老生代和永久代上升非常慢(因?yàn)楹枚鄬?duì)象到不了年老代就

被回收了),所以CMS執(zhí)行頻率非常低,好幾個(gè)小時(shí)才執(zhí)行一次,這樣,服務(wù)器都不用重啟了。

      -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的關(guān)系公式

     上面介紹了promontion faild產(chǎn)生的原因是Eden空間不足的情況下將Eden與From survivor中的存活對(duì)象存入To survivor區(qū)時(shí),To survivor區(qū)的空間不足,再次晉升到old gen區(qū),

而old gen區(qū)內(nèi)存也不夠的情況下產(chǎn)生了promontion faild從而導(dǎo)致full gc。那可以推斷出:eden+from survivor < old gen區(qū)剩余內(nèi)存時(shí),不會(huì)出現(xiàn)promontion faild的情況,即:

(Xmx-Xmn)*(1-CMSInitiatingOccupancyFraction/100)>=[Xmn-Xmn/(SurvivorRatior+2)]  進(jìn)而推斷出:

CMSInitiatingOccupancyFraction <={(Xmx-Xmn)-[Xmn-Xmn/(SurvivorRatior+2)]}/(Xmx-Xmn)*100

     例如:

     當(dāng)Xmx=128 Xmn=36 SurvivorRatior=1時(shí) CMSInitiatingOccupancyFraction<=((128.0-36)-(36-36/(1+2)))/(128-36)*100 =73.913

     當(dāng)Xmx=128 Xmn=24 SurvivorRatior=1時(shí) CMSInitiatingOccupancyFraction<=((128.0-24)-(24-24/(1+2)))/(128-24)*100=84.615…

     當(dāng)Xmx=3000 Xmn=600 SurvivorRatior=1時(shí)  CMSInitiatingOccupancyFraction<=((3000.0-600)-(600-600/(1+2)))/(3000-600)*100=83.33

     當(dāng)CMSInitiatingOccupancyFraction低于70% 需要調(diào)整Xmn或SurvivorRatior值。

 四、內(nèi)存泄露的分析

      HPjmeter是一個(gè)GC的圖形化分析工具,由上圖可以看出GC始終無(wú)法回收heap內(nèi)存空間,以使heap內(nèi)存空間的使用量持續(xù)升高,明顯存在內(nèi)存泄露的可能性。定位內(nèi)存泄露,可以生

成dump文件,并利用MAT進(jìn)行分析,查找原因。

     內(nèi)存分析工具:

     詳細(xì)信息可參考文章http://qa.taobao.com/?p=14264

     JVM內(nèi)存狀況查看方案及工具:http://www.51testing.com/html/58/n-237858.html

     使用 Eclipse Memory Analyzer 進(jìn)行堆轉(zhuǎn)儲(chǔ)文件分析:http://www.ibm.com/developerworks/cn/opensource/os-cn-ecl-ma/index.html?ca=drs-

     參考文獻(xiàn):

    1、JVM系列三:JVM參數(shù)設(shè)置、分析         http://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html 

    2、一個(gè)典型的OutOfMemory分析過(guò)程        http://hbase.iteye.com/blog/1356450

    3、使用MAT分析內(nèi)存泄露       http://qa.taobao.com/?p=14264

    4、JVM內(nèi)存狀況查看方法和分析工具      http://www.51testing.com/html/58/n-237858.html

    5、使用 Eclipse Memory Analyzer 進(jìn)行堆轉(zhuǎn)儲(chǔ)文件分析:http://www.ibm.com/developerworks/cn/opensource/os-cn-ecl-ma/index.html?ca=drs-