優(yōu)化CMS(concurrent garbage collection)
   使用CMS,old代的垃圾回收執(zhí)行線程會和應(yīng)用程序的線程最大程度的并發(fā)執(zhí)行。這個(gè)提供了一個(gè)機(jī)會來減少最壞延遲的頻率和最壞延遲的時(shí)間消耗。CMS沒有執(zhí)行壓縮,所以可以避免old代空間的stop-the-world壓縮(會讓整個(gè)應(yīng)用暫停運(yùn)行)。

   優(yōu)化CMS的目標(biāo)就是避開stop-the-world壓縮垃圾回收,然而,這個(gè)說比做起來容易。在一些的部署情況下,這個(gè)是不可避免的,尤其是當(dāng)內(nèi)存分配受限的時(shí)候。

   在一些特殊的情況下,CMS比其他類型的垃圾回收需要更多優(yōu)化,更需要優(yōu)化young代的空間,以及潛在的優(yōu)化該什么時(shí)候初始化old代的垃圾回收循環(huán)。

   當(dāng)從吞吐量垃圾回收器(Throughput)遷移到CMS的時(shí)候,有可能會獲得更慢的MinorGC,由于對象從young代轉(zhuǎn)移到old會更慢 ,由于CMS在old代里面分配的內(nèi)存是一個(gè)不連續(xù)的列表,相反,吞吐量垃圾回收器只是在本地線程的分配緩存里面指定一個(gè)指針。另外,由于old代的垃圾回收線程和應(yīng)用的線程是盡可能的并發(fā)運(yùn)行的,所以吞吐量會更小一些。然而,最壞的延遲的頻率會少很多,由于在old代的不可獲取的對象能夠在應(yīng)用運(yùn)行的過程被垃圾回收,這樣可以避免old代的空間溢出。

   使用CMS,如果old代能夠使用的空間有限,單線程的stop-the-world壓縮垃圾回收會執(zhí)行。這種情況下,F(xiàn)ullGC的時(shí)間會比吞吐量垃圾回收器的FullGC時(shí)間還要長,導(dǎo)致的結(jié)果是,CMS的絕對最差延遲會比吞吐量垃圾回收器的最差延遲嚴(yán)重很多。old代的空間溢出以及運(yùn)行了stop-the-world垃圾回收必須被應(yīng)用負(fù)責(zé)人重視,由于在響應(yīng)上會有更長的中斷。因此,不要讓old代運(yùn)行得溢出就非常重要了。對于從吞吐量垃圾回收器遷移到CMS的一個(gè)比較重要的建議就是提升old代20%到30%的容量。

   在優(yōu)化CMS的時(shí)候有幾個(gè)注意點(diǎn),首先,對象從young代轉(zhuǎn)移到old代的轉(zhuǎn)移率。其次,CMS重新分配內(nèi)存的概率。再次,CMS回收對象時(shí)候產(chǎn)生的old代的分隔,這個(gè)會在可獲得的對象中間產(chǎn)生一些空隙,從而導(dǎo)致了分隔空間。

   碎片可以被下面的幾種方法尋址。第一辦法是壓縮old代,壓縮old代空間是通過stop-the-world垃圾回收壓縮完成的,就像前面所說的那樣,stop-the-world垃圾回收會執(zhí)行很長時(shí)間,會嚴(yán)重影響應(yīng)用的響應(yīng)時(shí)間,應(yīng)該避開。第二種辦法是,對碎片編址,提高old代的空間,這個(gè)辦法不能完全解決碎片的問題的,但是可以延遲old代壓縮的時(shí)間。通常來講,old代越多內(nèi)存,由于碎片導(dǎo)致需要執(zhí)行的壓縮的時(shí)間久越長。努力把old的空間增大的目標(biāo)是在應(yīng)用的生命周期中,避免堆碎片導(dǎo)致stop-the-world壓縮垃圾回收,換句話說,應(yīng)用GC最大內(nèi)存原則。另外一種處理碎片的辦法是減少對象從young代移動(dòng)到old的概率,就是減少M(fèi)inorGC,應(yīng)用MinorGC回收原則。

   任期閥值(tenuring threshold)控制了對象該什么時(shí)候從young代移動(dòng)到old代。任期閥值會在后面詳細(xì)的介紹,它是HotSpot VM基于young代的占用空間來計(jì)算的,尤其是survivor(幸存者)空間的占用量。下面詳細(xì)介紹一下survivor空間以及討論任期閥值。

survivor空間

   survivor空間是young代的一部分,如下圖所示。young代被分成了一個(gè)eden區(qū)域和兩個(gè)survivor空間。
   
   兩個(gè)survivor空間的中一個(gè)被標(biāo)記為“from”,另外一個(gè)標(biāo)記為“to”。新的Java對象被分配到Eden空間。比如說,下面的一條語句:
   
[java] view plain copy
  1. <span style="font-size:14px;">   Map<String,String> map = new HashMap<String,String>();</span>  

   一個(gè)新的HashMap對象會被放到eden空間,當(dāng)eden空間滿了的時(shí)候,MinorGC就會執(zhí)行,任何存活的對象,都從eden空間復(fù)制到“to” survivor空間,任何在“from” survivor空間里面的存活對象也會被復(fù)制到“to” survivor。MinorGC結(jié)束的時(shí)候,eden空間和“from” survivor空間都是空的,“to” survivor空間里面存儲存活的對象,然后,在下次MinorGC的時(shí)候,兩個(gè)survivor空間交換他們的標(biāo)簽,現(xiàn)在是空的“from” survivor標(biāo)記成為“to”,“to” survivor標(biāo)記為“from”。因此,在MinorGC結(jié)束的時(shí)候,eden空間是空的,兩個(gè)survivor空間中的一個(gè)是空的。

   在MinorGC過程,如果“to” survivor空間不夠大,不能夠存儲所有的從eden空間和from suvivor空間復(fù)制過來活動(dòng)對象,溢出的對象會被復(fù)制到old代。溢出遷移到old代,會導(dǎo)致old代的空間快速增長,會導(dǎo)致stop-the-world壓縮垃圾回收,所以,這里要使用MinorGC回收原則。

   避免survivor空間溢出可以通過指定survivor空間的大小來實(shí)現(xiàn),以使得survivor有足夠的空間來讓對象存活足夠的歲數(shù)。高效的歲數(shù)控制會導(dǎo)致只有長時(shí)間存活的對象轉(zhuǎn)移到old代空間。

   歲數(shù)控制是指一個(gè)對象保持在young代里面直到無法獲取,所以讓old代只是存儲長時(shí)間保存的對象。

   survivor的空間可以大小設(shè)置可以用HotSpot命令行參數(shù):-XX:SurvivorRatio=<ratio>

   <ratio>必須是以一個(gè)大于0的值,-XX:SurvivorRatio=<ratio>表示了每一個(gè)survivor的空間和eden空間的比值。下面這個(gè)公式可以用來計(jì)算survivor空間的大小

   
[html] view plain copy
  1. survivor spave size = -Xmn<value>/(-XX:SurvivorRatio=<ratio>+2)  

   這里有一個(gè)+2的理由是有兩個(gè)survivor空間,是一個(gè)調(diào)節(jié)參數(shù)。ratio設(shè)置的越大,survivor的空間越小。為了說明這個(gè)問題,假設(shè)young代的大小是-Xmn512m而且-XX:SurvivorRatio=6.那么,young代有兩個(gè)survivor空間且空間大小是64M,那么eden空間的大小是384M。

   同樣假如young代的大小是512M,但是修改-XX:SurvivorRatio=2,這樣的配置會使得每一個(gè)survivor空間的大小是128m而eden空間的大小是256M。

   對于一個(gè)給定大小young代空間大小,減小ratio參數(shù)增加survivor空間的大小而且減少eden空間的大小。反之,增加ratio會導(dǎo)致survivor空間減少而且eden空間增大。減少eden空間會導(dǎo)致MinorGC更加頻繁,相反,增加eden空間的大小會導(dǎo)致更小的MinorGC,越多的MinorGC,對象的歲數(shù)增長得越快。

   為了更好的優(yōu)化survivor空間的大小和完善young代空間的大小,需要監(jiān)控任期閥值,任期閥值決定了對象會再young代保存多久。怎么樣來監(jiān)控和優(yōu)化任期閥值將在下一節(jié)中介紹。
   
任期閥值

   “任期”是轉(zhuǎn)移的代名詞,換句話說,任期閥值意味著對象移動(dòng)到old代空間里面。HotSpot VM每次MinorGC的時(shí)候都會計(jì)算任期,以決定對象是否需要移動(dòng)到old代去。任期閥值就是對象的歲數(shù)。對象的歲數(shù)是指他存活過的MinorGC次數(shù)。當(dāng)一個(gè)對象被分配的時(shí)候,它的歲數(shù)是0。在下次MinorGC的時(shí)候之后,如果對象還是存活在young代里面,它的歲數(shù)就是1。如果再經(jīng)歷過一次MinorGC,它的歲數(shù)變成2,依此類推。在young代里面的歲數(shù)超過HotSpot VM指定閥值的對象會被移動(dòng)到old代里面。換句話說,任期閥值決定對象在young代里面保存多久。

   任期閥值的計(jì)算依賴于young代里面能夠存放的對象數(shù)以及MinorGC之后,“to” servivor的空間占用。HotSpot VM有一個(gè)選項(xiàng)-XX:MaxTenuringThreshold=<n>,可以用來指定當(dāng)時(shí)對象的歲數(shù)超過<n>的時(shí)候,HotSpot VM會把對象移動(dòng)到old代去。內(nèi)部計(jì)算的任期閥值一定不會超過指定的最大任期閥值。最大任期閥值在可以被設(shè)定為0-15,不過在Java 5 update 5之前可以設(shè)置為1-31。

   不推薦把最大任期閥值設(shè)定成0或者超過15,這樣會導(dǎo)致GC的低效率。

   如果HotSpot VM它無法保持目標(biāo)survivor 空間的占用量,它會使用一個(gè)小于最大值的任期閥值來維持目標(biāo)survivor空間的占用量,任何比這個(gè)任期閥值的大的對象都會被移動(dòng)到old代。話句話說,當(dāng)存活對象的量大于目標(biāo)survivor空間能夠接受的量的時(shí)候,溢出發(fā)生了,溢出會導(dǎo)致對象快速的移動(dòng)到old代,導(dǎo)致不期望的FullGC。甚至?xí)?dǎo)致更頻繁的stop-the-world壓縮垃圾回收。哪些對象會被移動(dòng)到old代是根據(jù)評估對象的歲數(shù)和任期閥值來確定的。因此,很有必要監(jiān)控任期閥值以避免survivor空間溢出,接下來詳細(xì)討論。

監(jiān)控任期閥值

   為了不被內(nèi)部計(jì)算的任期閥值迷惑,我們可以使用命令選項(xiàng)-XX:MaxTenuringThreshod=<n>來指定最大的任期閥值。為了決定出最大的任期閥值,需要監(jiān)控任期閥值的分布和對象歲數(shù)的分布,通過使用下面的選項(xiàng)實(shí)現(xiàn)

 
[html] view plain copy
  1. -XX:+PrintTenuringDistribution  

   -XX:+PrintTenuringDistribution的輸出顯示在survivor空間里面有效的對象的歲數(shù)情況。閱讀-XX:+PrintTenuringDistribution輸出的方式是觀察在每一個(gè)歲數(shù)上面,對象的存活的數(shù)量,以及其增減情況,以及HotSpot VM計(jì)算的任期閥值是不是等于或者近似于設(shè)定的最大任期閥值。

   -XX:+PrintTenuringDistribution在MinorGC的時(shí)候產(chǎn)生任期分布信息。它可以同其他選項(xiàng)一同使用,比如-XX:+PrintGCDateStamps,-XX:+PrintGCTimeStamps以及-XX:+PringGCDetails。當(dāng)調(diào)整survivor空間大小以獲得有效的對象歲數(shù)分布,你應(yīng)該使用-XX:+PrintTenuringDistribution。在生產(chǎn)環(huán)境中,它同樣非常有用,可以用來判斷stop-the-world的垃圾回收是否發(fā)生。

   下面是一個(gè)輸出的例子:

   Desired survivor size 8388608 bytes, new threshold 1 (max 15) 
   - age 1: 16690480 bytes, 16690480 total

   在這里例子中,最大任期閥值被設(shè)置為15,(通過max 15表示)。內(nèi)部計(jì)算出來的任期閥值是1,通過threshold 1表示。Desired survivor size 8388608 bytes表示一個(gè)survivor的空間大小。目標(biāo)survivor的占有率是指目標(biāo)survivor和兩個(gè)survivor空間總和的比值。怎么樣指定期望的survivor空間大小在后面會詳細(xì)介紹。在第一行下面,會列出一個(gè)對象的歲數(shù)列表。每行會列出每一個(gè)歲數(shù)的字節(jié)數(shù),在這個(gè)例子中,歲數(shù)是1的對象有16690480字節(jié),而且每行后面有一個(gè)總的字節(jié)數(shù),如果有多行輸出的話,總字節(jié)數(shù)是前面的每行的累加數(shù)。后面舉例說明。

   在前面的例子中,由于期望的survivor大小(8388608)比實(shí)際總共survivor字節(jié)數(shù)(16690480)小,也就是說,survivor空間溢出了,這次MinorGC會有一些對象移動(dòng)到old代。這個(gè)就意味著survivor的空間太小了。另外,設(shè)定的最大任期閥值是15,但是實(shí)際上JVM使用的是1,也表明了survivor的空間太小了。

   如果發(fā)現(xiàn)survivor區(qū)域太小,就增大survivor的空間,下面詳細(xì)介紹如何操作。
   
設(shè)定survivor空間

   當(dāng)修改survivor空間的大小的時(shí)候,有一點(diǎn)需要記住。當(dāng)修改survivor空間大小的時(shí)候,如果young代的大小不改變,那么eden空間會減小,進(jìn)一步會導(dǎo)致更頻繁的MinorGC。因此,增加survivor空間的時(shí)候,如果young代的空間大小違背了MinorGC頻率的需求,eden空間的大小同需要需要增加。換句話說,當(dāng)survivor空間增加的時(shí)候,young代的大小需要增加。

   如果有空間來增加MinorGC的頻率,有兩種選擇,一是拿一些eden空間來增加survivor的空間,二是讓young的空間更大一些。常規(guī)來講,更好的選擇是如果有可以使用的內(nèi)存,增加young代的空間會比減少eden的空間更好一些。讓eden空間大小保持恒定,MinorGC的頻率不會改變,即使調(diào)整survivor空間的大小。

   使用-XX:+PrintTenuringDistribution選項(xiàng),對象的總字節(jié)數(shù)和目標(biāo)survivor空間占用可以用來計(jì)算survivor空間的大小。重復(fù)前面的例子:
   Desired survivor size 8388608 bytes, new threshold 1 (max 15) 
   - age 1: 16690480 bytes, 16690480 total

   存活對象的總字節(jié)數(shù)是1669048,這個(gè)并發(fā)垃圾回收器(CMS)的目標(biāo)survivor默認(rèn)使用50%的survivor空間。通過這個(gè)信息,我們可以知道survivor空間至少應(yīng)該是33380960字節(jié),大概是32M。這個(gè)計(jì)算讓我們知道對survivor空間的預(yù)估值需要計(jì)算對象的歲數(shù)更高效以及防止溢出。為了更好的預(yù)估survivor的可用空間,你應(yīng)該監(jiān)控應(yīng)用穩(wěn)定運(yùn)行情況下的任期分布,并且使用所有的額外總存活對象的字節(jié)數(shù)來作為survivor空間的大小。

   在這個(gè)例子,為了讓應(yīng)用計(jì)算歲數(shù)更加有效,survivor空間需要至少提升32M。前面使用的選項(xiàng)是:

 
[html] view plain copy
  1. -Xmx1536m -Xms1536m -Xmn512m -XX:SurvivorRatio=30  

   那么為了保持MinorGC的頻率不發(fā)生變化,然后增加survivor空間的大小到32M,那么修改后的選項(xiàng)如下:

 
[html] view plain copy
  1. -Xmx1568m -Xms1568m -Xmn544m -XX:SurvivvorRatio=15  

   當(dāng)時(shí)young代空間增加了,eden空間的大小保持大概相同,且survivor的空間大小增減了。需要注意的時(shí)候,-Xmx、-Xms、-Xmn都增加了32m。另外,-XX:SurvivvorRatio=15讓每一個(gè)survivor空間的大小都是32m (544/(15+2) = 32)。

   如果存在不能增加young代空間大小的限制,那么增加survivor空間大小需要以減少eden空間的大小為代價(jià)。下面是一個(gè)增加survivor空間大小,每一個(gè)survivor空間從16m增減加到32m,那么會見減少eden的空間,從480m減少到448m(512-32-32=448,512-16-16=480)。

   
[html] view plain copy
  1. -Xms1536m -Xms1536m -Xmn1512m -XX:SurvivorRatio=14  

   再次強(qiáng)調(diào),減少eden空間大小會增加MinorGC的頻率。但是,對象會在young代里面保持更長的時(shí)間,由于提升survivor的空間。

   假如運(yùn)行同樣的應(yīng)用,我們保持eden的空間不變,增加survivor空間的大小,如下面選項(xiàng):

 
[html] view plain copy
  1. <span style="font-size:14px;"> -Xmx1568m -Xms1568m -Xmn544m -XX:SurvivorRatio=15</span>  

   可以產(chǎn)生如下的任期分布:
   Desired survivor size 16777216 bytes, new threshold 15 (max 15)
- age 1: 6115072 bytes, 6115072 total
- age 2: 286672 bytes, 6401744 total
- age 3: 115704 bytes, 6517448 total
- age 4: 95932 bytes, 6613380 total
- age 5: 89465 bytes, 6702845 total
- age 6: 88322 bytes, 6791167 total
- age 7: 88201 bytes, 6879368 total
- age 8: 88176 bytes, 6967544 total
- age 9: 88176 bytes, 7055720 total
- age 10: 88176 bytes, 7143896 total
- age 11: 88176 bytes, 7232072 total
- age 12: 88176 bytes, 7320248 total

   從任期分布的情況來看,survivor空間沒有溢出,由于存活的總大小是7320248,但是預(yù)期的survivor空間大小是16777216以及任期閥值和最大任期閥值是相等的。這個(gè)表明,對象的老化速度是高效的,而且survivor空間沒有溢出。

   在這個(gè)例子中,由于歲數(shù)超過3的對象很少,你可能像把最大任期閥值設(shè)置為3來測試一下,即設(shè)置選項(xiàng)-XX:MaxTenuringThreshhold=3,那么整個(gè)選項(xiàng)可以設(shè)置為:

 
[html] view plain copy
  1. -Xmx1568m -Xms1658m -Xmn544m -XX:SurvivorRatio=15 -XX:MaxTenuringThreshold=3  

   這個(gè)選項(xiàng)設(shè)置和之前的選項(xiàng)設(shè)置的權(quán)衡是,后面這個(gè)選擇可以避免在MinorGC的時(shí)候不必要地把對象從“from” survivor復(fù)制到“to” survivor。在應(yīng)用運(yùn)行在穩(wěn)定狀態(tài)的情況下,觀察多次MinorGC任期分布情況,看是否有對象最終移動(dòng)到old代或者顯示的結(jié)果還是和前面的結(jié)果類似。如果你觀察得到和前面的任期分布情況相同,基本沒有對象的歲數(shù)達(dá)到15,也沒有survivor的空間溢出,你應(yīng)該自己設(shè)置最大任期閥值以代替JVM默認(rèn)的15。在這個(gè)例子中,沒有長時(shí)間存活的對象,由于在他們的歲數(shù)沒有到達(dá)15的時(shí)候就被垃圾回收了。這些對象在MinorGC中被回收了,而不是移動(dòng)到old代里面。使用并發(fā)垃圾回收(CMS)的時(shí)候,對象從young代移動(dòng)到old代最終會導(dǎo)致old的碎片增加,有可能導(dǎo)致stop-the-world壓縮垃圾回收,這些都是不希望出現(xiàn)的。寧可選擇讓對象在“from” survivor和“to” survivor中復(fù)制,也不要太快的移動(dòng)到old代。

   你可能需要重復(fù)數(shù)次監(jiān)控任期分布、修改survivor空間大小或者重新配置young代的空間大小直到你對應(yīng)用由于MinorGC引起的延遲滿意為止。如果你發(fā)現(xiàn)MinorGC的時(shí)間太長,你可以通過減少young代的大小直到你滿意為止。盡管,減少young代的大小,會導(dǎo)致更快地移動(dòng)對象到old代,可能導(dǎo)致更多的碎片,如果CMS的并發(fā)垃圾回收能夠跟上對象的轉(zhuǎn)移率,這種情況就比不能滿足應(yīng)用的延遲需求更好。如果這步不能滿足應(yīng)用的MinorGC的延遲和頻率需求,這個(gè)時(shí)候就有必要重新審視需求以及修改應(yīng)用程序了。

   如果滿足對MinorGC延遲的需求,包括延遲時(shí)間和延遲頻率,你可以進(jìn)入下一步,優(yōu)化CMS垃圾回收周期的啟動(dòng),下節(jié)詳細(xì)介紹。