天天看點

一步步優化JVM五:優化延遲或者響應時間

本節的目标是做一些優化以滿足對應用對延遲的需求。這次需要幾個步驟,包括完善java堆大小的配置,評估垃圾回收占用的時間和頻率,也許還要嘗試切換到不同的垃圾回收器,以及由于使用了不同的垃圾回收器,需要重新優化java堆空間大小。

    這一步有如下可能的結果:

    1、應用的延遲需求被滿足了。如果這一步的優化操作滿足了應用的延遲需求,你可以繼續下一步優化(優化吞吐量)。

    2、應用的延遲需求未被滿足。如果這一步的優化操作未能滿足延遲需求,你可能需要重新看看延遲需求是否合理或者修改應用程式。一些可能的問題可以幫助改善應用的延遲問題:

    a、優化java堆以及修改應用以減少對象的配置設定和對象的長時間存活。

    b、修改jvm的部署結構,讓每一個jvm做更少的工作。

    上面的兩個步驟都可以減少jvm的對象配置設定,是以減少垃圾回收的頻率。

    這一步從檢視垃圾回收對應用的延遲的影響開始,基于前面一節“決定記憶體消耗”計算出來的java堆大小。

    下面列出了評估垃圾回收對延遲的影響需要進行的幾個事情:

    1、測量minorgc的時間。

    2、測量minorgc的頻率。

    3、測量fullgc的時間。

    4、測量fullgc的頻率。

    測量垃圾回收的時間的和頻率對于改善java堆大小配置來說是非常重要的。minorgc的時間和頻率的測量結果可以用來改善young代的空間大小。測量最壞情況下fullgc的時間和頻率可以用來決定old代的大小,以及是否需要切換成吞吐量垃圾回收器(通過使用-xx:+useparalleoldgc或者-xx:+useparallelgc)或者并發垃圾回收器(cms,通過使用-xx:+useconcmarksweepgc)。在使用吞吐量垃圾回收器的時候,如果垃圾回收的延遲和頻率太高以導緻應用的延遲需求無法滿足的時候才切換到cms,如果選擇了切換,需要對cms垃圾回收器進行優化,後面會詳細介紹這個問題。

    接下來詳細介紹前面提到的各種情況。

    需求

    下面列舉了幾個這一步優化操作需求,它們來源于應用的系統需求:

    1、可以接收的平均暫停時間。平均暫停時間需求用于和minorgc消耗的時間比較。

    2、可以接收的minorgc的頻率。其實頻道對于應用負責人來說,沒有平均延遲時間重要。

    3、應用負責人能夠接受的最大延遲時間。這個時間受到fullgc的影響。

    4、應用負責人能夠接收的最大延遲的頻率,即fullgc的頻率。其實,大多數時間應用管理者還是更加關心應用的的最大延遲時間超過了最大延遲的頻率。

一旦确定了需求,這些垃圾回收器的時間消耗和頻率都可以通過垃圾回收日志收集到。先把垃圾回收器設定為吞吐量垃圾回收器(設定-xx:+useparalleloldegc或者-xx:+useparallelgc)。通過反複測試,可以讓young代和old代滿足上面的要求。下面2節介紹如何優化young代和old代空間大小來觀察minorgc和最壞情況的fullgc的消耗時間和頻率。

    改善young代的大小

    确定young代的大小是通過評估垃圾回收的統計資訊以及觀察minorgc的消耗時間和頻率,下面舉例說明如何通過垃圾回收的統計資訊來确定young代的大小。

盡管minorgc消耗的時間和young代裡面的存活的對象數量有直接關系,但是一般情況下,更小young代空間,更短的minorgc時間。如果不考慮minorgc的時間消耗,減少young代的大小會導緻minorgc變得更加頻繁,由于更小的空間,用玩空間會用更少的時間。同理,提高young代的大小會降低minorgc的頻率。

    當測試垃圾回收資料的時候,發現minorgc的時間太長了,正确的做法就是減少young代的空間大小。如果minorgc太頻繁了就增加young代的空間大小。

一步步優化JVM五:優化延遲或者響應時間

    上圖是一個展示了minorgc的例子,這個例子是運作在如下的hotspot vm指令參數下的。

一步步優化JVM五:優化延遲或者響應時間

-xms6144m -xmx6144m -xmn2048m -xx:permsize=96m -xx:maxpermsize=96m -xx:+userparalleloldgc  

    上圖顯示了minorgc平均的消耗時間是0.05秒,平均的頻率是2.147秒1次。當計算minorgc的消耗時間和頻率的時候,越多的資料參與計算,準确性會越高。并且應用要處于穩定運作狀态下來收集minorgc資訊也是非常重要的。

    下一步是比較minorgc的平均時間和系統對延遲的要求,如果minorgc的平均時間大于了系統的要求,減少young代的空間大小,然後繼續測試,再收集資料以及重新評估。

    如果minorgc的頻率大于了系統的要求,就增加young代的空間大小,然後繼續測試,再收集以及重新評估。

    也許需要數次重複才能夠讓系統達到延遲要求。當你改變young代的空間大小的時候,盡量保持old代的空間大小不要改變。

    從上圖的垃圾回收資訊來看,如果應用的延遲要求是40毫秒的話,觀察到的minorgc的延遲是58毫秒,比系統的要求高出了不少。上面例子使用的指令選項是

一步步優化JVM五:優化延遲或者響應時間

    意味着old代的空間大小是4096m,減小young代的空間大小的10%而且要保持old代的空間大小不變,可以使用如下選項。

一步步優化JVM五:優化延遲或者響應時間

-xms5940m -xmx5940m -xmn1844m -xx:permsize=96 -xx:maxpermsize=96 -xx:+userparalleloldgc  

    注意的是young代的空間大小從2048m減少到1844m,整個java堆的大小從6144m減少到5940m,兩者都是減少了204m。

    無論是young的空間調大還是調小,都需要重新收集垃圾回收資訊和重新計算minorgc的平均時間和頻率,以達到應用的延遲要求,可能需要幾個輪回來達到這個要求。

為了說明了增加young代的大小以降低minorgc的頻率,我們下面舉一個例子。如果系統要求的頻率是5秒一次,這個上面的例子中是2.147秒一次,也就是說它用了2.147秒,填充滿了2048m空間,如果需要5秒一次的頻率,那麼就需要5/2.147倍的空間,即2048*5/2.147等于4700m。是以young代的空間需要調整到4700m。下面是一個示例來說明配置這個:

一步步優化JVM五:優化延遲或者響應時間

-xms8796m -xmx8796m -xmn4700m -xx:permsize=96m -xx:maxpermsize=96m -xx:+usepralleloldgc  

    注意是-xms和-xmx也同步調整了。

    另外一些調整young代的空間需要注意的事項:

    1、old代的空間一定不能小于活動對象的大小的1.5倍。

    2、young代的空間至少要有java堆大小的10%,太小的java空間會導緻過于頻繁的minorgc。

    3、當提高java堆大小的時候,不要超過jvm可以使用的實體記憶體大小。如果使用過多的實體記憶體,會導緻使用交換區,這個會嚴重影響性能。

    如果在僅僅是minorgc導緻了延遲的情況下,你無法通過調整young代的空間來滿足系統的需求,那麼你需要重 新修改應用程式、修改jvm部署模型把應用部署到多個jvm上面(通常得要多機器了)或者重新評估系統的需求。

    如果通過調整minorgc能夠滿足應用的延遲需求,接下來就可以調整old代了,以達到最壞情況下的延遲和延遲頻率的需求。下一節詳細說明這個問題。

完善old代的大小

    這一節的目标是評估由于fullgc引起的最差暫停時間和頻率。

    同前面一個節“完善young代大小”一樣,垃圾回收的統計資訊是必須的,在穩定狀态下,fullgc的時間表明了應用最差的延遲,如果發生了多個fullgc,計算多個fullgc的平均消耗時間,更多資料能夠更好的評估。

    計算兩次不同的fullgc之間的時間差,可以提供出fullgc的頻率,下圖用一個列子來說明兩個fullgc:

一步步優化JVM五:優化延遲或者響應時間

    如果沒有fullgc,可以人為的去幹預,前面說過,可以使用visualvm來觸發fullgc。另外,評估fullgc的頻率需要知道對象的轉移率,這個轉移率說明對象從young代轉移到old代。接下來的介紹如何評估轉移率。

    接下有個幾個minorgc的例子,他們被用來評估fullgc的頻率。

一步步優化JVM五:優化延遲或者響應時間
2010-12-05t14:40:29.564-0800: [gc   [psyounggen: 2045989k->249795k(2097152k)]   3634533k->1838430k(6291456k), 0.0543798 secs]   [times: user=0.38 sys=0.01, real=0.05 secs]  
一步步優化JVM五:優化延遲或者響應時間
2010-12-05t14:40:31.949-0800: [gc   [psyounggen: 2047896k->247788k(2097152k)]   3655319k->1859216k(6291456k), 0.0539614 secs]   [times: user=0.35 sys=0.01, real=0.05 secs]  
一步步優化JVM五:優化延遲或者響應時間
2010-12-05t14:40:34.346-0800 [gc   [psyounggen: 2045889k->248993k(2097152k)]   3677202k->1881099k(6291456k), 0.0532377 secs]   [times: user=0.39 sys=0.01, real=0.05 secs]  
一步步優化JVM五:優化延遲或者響應時間
2010-12-05t14:40:36.815-0800 [gc   [psyounggen: 2047094k->247765k(2097152k)]   3696985k->1900882k(6291456k), 0.0543332 secs]   [times: user=0.37 sys=0.01, real=0.05 secs]  

    從上面的例子可以看出:

    1、java堆的大小是6291456k或6144m

    2、young代的大小是2097152k或2048m

    3、old代的大小是6144m-2048m = 4096m

    在這個例子中,活動對象的大小差不多是1370m。那麼old代還有2726m剩餘空間(4096m-1370m=2726m)。

填充完成2736m空間需要多長時間是由young代向old代的轉移率決定的。這個轉移率的計算通過檢視每次minorgc後old代的占用空間的增長情況以及minorgc發生的時間。old代的空間占用是minorgc之後java堆中對象大小減去young代的大小,通過這個公式計算,可以看出在這個例子中每次minorgc之後,old代的空間占用情況是:

    1588635k,第一個minorgc

    1611428k,第二次minorgc

    1632106k,第三次minorgc

    1653117k,第四次minorgc

    每次的增量分别是

    22793k,第一次和第二次的增量

    20678k,第二次和第三次的增量

    21011k,第三次和第四次的增量

    平均每次minorgc轉移大概201494k或者叫21m。

    如果剩餘的空間都是按照設個轉移率來轉移到old代的話,且知道minorgc的頻率是每2.147秒一次。是以,這個轉移率是201494k/2.147s差不多10m/s,那麼一共的空間是2736m空間需要273.6s差不多4.5分鐘一次。

    是以,通過前面的案例分析,應用的最差延遲的頻率是4.5分鐘。這個評估可以通過讓應用處于穩定運作狀态超過4.5分鐘來驗證。

    如果評估和觀察的fullgc的頻率高于了應用對最壞延遲頻率的要求,那麼可以提高old代的空間大小。如果改變old代的大小,保持young代的空間恒定,在優化young代的時候也說這個問題,兩者應該獨立優化,以保證有高效。

    如果這步已經達到了你最壞延遲的要求,那麼這一步調優延遲就算已經完成了,就可以進入下一步去調優“吞吐量”了。

如果你未能達到了應用對最壞延遲時間和頻率的性能要求,由于fullgc的執行時間太長了,然後你可以把垃圾回收器切換cms(concurrent

garbage

collection)。cms有能力讓垃圾回收盡量是多線程的,即讓程式保持在運作狀态。要使用cms可以通過下面這條指令選項:-xx:+useconcmarksweepgc。

    後面詳細說明如何調優cms。

優化cms(concurrent garbage collection)

   使用cms,old代的垃圾回收執行線程會和應用程式的線程最大程度的并發執行。這個提供了一個機會來減少最壞延遲的頻率和最壞延遲的時間消耗。cms沒有執行壓縮,是以可以避免old代空間的stop-the-world壓縮(會讓整個應用暫停運作)。

   優化cms的目标就是避開stop-the-world壓縮垃圾回收,然而,這個說比做起來容易。在一些的部署情況下,這個是不可避免的,尤其是當記憶體配置設定受限的時候。

   在一些特殊的情況下,cms比其他類型的垃圾回收需要更多優化,更需要優化young代的空間,以及潛在的優化該什麼時候初始化old代的垃圾回收循環。

   當從吞吐量垃圾回收器(throughput)遷移到cms的時候,有可能會獲得更慢的minorgc,由于對象從young代轉移到old會更慢 ,由于cms在old代裡面配置設定的記憶體是一個不連續的清單,相反,吞吐量垃圾回收器隻是在本地線程的配置設定緩存裡面指定一個指針。另外,由于old代的垃圾回收線程和應用的線程是盡可能的并發運作的,是以吞吐量會更小一些。然而,最壞的延遲的頻率會少很多,由于在old代的不可擷取的對象能夠在應用運作的過程被垃圾回收,這樣可以避免old代的空間溢出。

 使用cms,如果old代能夠使用的空間有限,單線程的stop-the-world壓縮垃圾回收會執行。這種情況下,fullgc的時間會比吞吐量垃圾回收器的fullgc時間還要長,導緻的結果是,cms的絕對最差延遲會比吞吐量垃圾回收器的最差延遲嚴重很多。old代的空間溢出以及運作了stop-the-world垃圾回收必須被應用負責人重視,由于在響應上會有更長的中斷。是以,不要讓old代運作得溢出就非常重要了。對于從吞吐量垃圾回收器遷移到cms的一個比較重要的建議就是提升old代20%到30%的容量。

   在優化cms的時候有幾個注意點,首先,對象從young代轉移到old代的轉移率。其次,cms重新配置設定記憶體的機率。再次,cms回收對象時候産生的old代的分隔,這個會在可獲得的對象中間産生一些空隙,進而導緻了分隔空間。

 碎片可以被下面的幾種方法尋址。第一辦法是壓縮old代,壓縮old代空間是通過stop-the-world垃圾回收壓縮完成的,就像前面所說的那樣,stop-the-world垃圾回收會執行很長時間,會嚴重影響應用的響應時間,應該避開。第二種辦法是,對碎片編址,提高old代的空間,這個辦法不能完全解決碎片的問題的,但是可以延遲old代壓縮的時間。通常來講,old代越多記憶體,由于碎片導緻需要執行的壓縮的時間久越長。努力把old的空間增大的目标是在應用的生命周期中,避免堆碎片導緻stop-the-world壓縮垃圾回收,換句話說,應用gc最大記憶體原則。另外一種處理碎片的辦法是減少對象從young代移動到old的機率,就是減少minorgc,應用minorgc回收原則。

 任期閥值(tenuring

threshold)控制了對象該什麼時候從young代移動到old代。任期閥值會在後面詳細的介紹,它是hotspot

vm基于young代的占用空間來計算的,尤其是survivor(幸存者)空間的占用量。下面詳細介紹一下survivor空間以及讨論任期閥值。

survivor空間

   survivor空間是young代的一部分,如下圖所示。young代被分成了一個eden區域和兩個survivor空間。

一步步優化JVM五:優化延遲或者響應時間

   兩個survivor空間的中一個被标記為“from”,另外一個标記為“to”。新的java對象被配置設定到eden空間。比如說,下面的一條語句:

一步步優化JVM五:優化延遲或者響應時間

<span style="margin: 0px; padding: 0px; font-size: 14px;">   map<string,string> map = new hashmap<string,string>();</span>  

 一個新的hashmap對象會被放到eden空間,當eden空間滿了的時候,minorgc就會執行,任何存活的對象,都從eden空間複制到“to”

survivor空間,任何在“from” survivor空間裡面的存活對象也會被複制到“to”

survivor。minorgc結束的時候,eden空間和“from” survivor空間都是空的,“to”

survivor空間裡面存儲存活的對象,然後,在下次minorgc的時候,兩個survivor空間交換他們的标簽,現在是空的“from”

survivor标記成為“to”,“to”

survivor标記為“from”。是以,在minorgc結束的時候,eden空間是空的,兩個survivor空間中的一個是空的。

 在minorgc過程,如果“to” survivor空間不夠大,不能夠存儲所有的從eden空間和from

suvivor空間複制過來活動對象,溢出的對象會被複制到old代。溢出遷移到old代,會導緻old代的空間快速增長,會導緻stop-the-world壓縮垃圾回收,是以,這裡要使用minorgc回收原則。

   避免survivor空間溢出可以通過指定survivor空間的大小來實作,以使得survivor有足夠的空間來讓對象存活足夠的歲數。高效的歲數控制會導緻隻有長時間存活的對象轉移到old代空間。

   歲數控制是指一個對象保持在young代裡面直到無法擷取,是以讓old代隻是存儲長時間儲存的對象。

   survivor的空間可以大小設定可以用hotspot指令行參數:-xx:survivorratio=<ratio>

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

一步步優化JVM五:優化延遲或者響應時間

survivor spave size = -xmn<value>/(-xx:survivorratio=<ratio>+2)  

 這裡有一個+2的理由是有兩個survivor空間,是一個調節參數。ratio設定的越大,survivor的空間越小。為了說明這個問題,假設young代的大小是-xmn512m而且-xx:survivorratio=6.那麼,young代有兩個survivor空間且空間大小是64m,那麼eden空間的大小是384m。

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

 對于一個給定大小young代空間大小,減小ratio參數增加survivor空間的大小而且減少eden空間的大小。反之,增加ratio會導緻survivor空間減少而且eden空間增大。減少eden空間會導緻minorgc更加頻繁,相反,增加eden空間的大小會導緻更小的minorgc,越多的minorgc,對象的歲數增長得越快。

   為了更好的優化survivor空間的大小和完善young代空間的大小,需要監控任期閥值,任期閥值決定了對象會再young代儲存多久。怎麼樣來監控和優化任期閥值将在下一節中介紹。

任期閥值

 “任期”是轉移的代名詞,換句話說,任期閥值意味着對象移動到old代空間裡面。hotspot

vm每次minorgc的時候都會計算任期,以決定對象是否需要移動到old代去。任期閥值就是對象的歲數。對象的歲數是指他存活過的minorgc次數。當一個對象被配置設定的時候,它的歲數是0。在下次minorgc的時候之後,如果對象還是存活在young代裡面,它的歲數就是1。如果再經曆過一次minorgc,它的歲數變成2,依此類推。在young代裡面的歲數超過hotspot

vm指定閥值的對象會被移動到old代裡面。換句話說,任期閥值決定對象在young代裡面儲存多久。

 任期閥值的計算依賴于young代裡面能夠存放的對象數以及minorgc之後,“to” servivor的空間占用。hotspot

vm有一個選項-xx:maxtenuringthreshold=<n>,可以用來指定當時對象的歲數超過<n>的時候,hotspot

vm會把對象移動到old代去。内部計算的任期閥值一定不會超過指定的最大任期閥值。最大任期閥值在可以被設定為0-15,不過在java 5

update 5之前可以設定為1-31。

   不推薦把最大任期閥值設定成0或者超過15,這樣會導緻gc的低效率。

 如果hotspot vm它無法保持目标survivor

空間的占用量,它會使用一個小于最大值的任期閥值來維持目标survivor空間的占用量,任何比這個任期閥值的大的對象都會被移動到old代。話句話說,當存活對象的量大于目标survivor空間能夠接受的量的時候,溢出發生了,溢出會導緻對象快速的移動到old代,導緻不期望的fullgc。甚至會導緻更頻繁的stop-the-world壓縮垃圾回收。哪些對象會被移動到old代是根據評估對象的歲數和任期閥值來确定的。是以,很有必要監控任期閥值以避免survivor空間溢出,接下來詳細讨論。

監控任期閥值

   為了不被内部計算的任期閥值迷惑,我們可以使用指令選項-xx:maxtenuringthreshod=<n>來指定最大的任期閥值。為了決定出最大的任期閥值,需要監控任期閥值的分布和對象歲數的分布,通過使用下面的選項實作

一步步優化JVM五:優化延遲或者響應時間

-xx:+printtenuringdistribution  

 -xx:+printtenuringdistribution的輸出顯示在survivor空間裡面有效的對象的歲數情況。閱讀-xx:+printtenuringdistribution輸出的方式是觀察在每一個歲數上面,對象的存活的數量,以及其增減情況,以及hotspot

vm計算的任期閥值是不是等于或者近似于設定的最大任期閥值。

 -xx:+printtenuringdistribution在minorgc的時候産生任期分布資訊。它可以同其他選項一同使用,比如-xx:+printgcdatestamps,-xx:+printgctimestamps以及-xx:+pringgcdetails。當調整survivor空間大小以獲得有效的對象歲數分布,你應該使用-xx:+printtenuringdistribution。在生産環境中,它同樣非常有用,可以用來判斷stop-the-world的垃圾回收是否發生。

   下面是一個輸出的例子:

   desired survivor size 8388608 bytes, new threshold 1 (max 15) 

   - age 1: 16690480 bytes, 16690480 total

   在這裡例子中,最大任期閥值被設定為15,(通過max 15表示)。内部計算出來的任期閥值是1,通過threshold 1表示。desired

survivor size 8388608

bytes表示一個survivor的空間大小。目标survivor的占有率是指目标survivor和兩個survivor空間總和的比值。怎麼樣指定期望的survivor空間大小在後面會詳細介紹。在第一行下面,會列出一個對象的歲數清單。每行會列出每一個歲數的位元組數,在這個例子中,歲數是1的對象有16690480位元組,而且每行後面有一個總的位元組數,如果有多行輸出的話,總位元組數是前面的每行的累加數。後面舉例說明。

   在前面的例子中,由于期望的survivor大小(8388608)比實際總共survivor位元組數(16690480)小,也就是說,survivor空間溢出了,這次minorgc會有一些對象移動到old代。這個就意味着survivor的空間太小了。另外,設定的最大任期閥值是15,但是實際上jvm使用的是1,也表明了survivor的空間太小了。

   如果發現survivor區域太小,就增大survivor的空間,下面詳細介紹如何操作。

設定survivor空間

 當修改survivor空間的大小的時候,有一點需要記住。當修改survivor空間大小的時候,如果young代的大小不改變,那麼eden空間會減小,進一步會導緻更頻繁的minorgc。是以,增加survivor空間的時候,如果young代的空間大小違背了minorgc頻率的需求,eden空間的大小同需要需要增加。換句話說,當survivor空間增加的時候,young代的大小需要增加。

 如果有空間來增加minorgc的頻率,有兩種選擇,一是拿一些eden空間來增加survivor的空間,二是讓young的空間更大一些。正常來講,更好的選擇是如果有可以使用的記憶體,增加young代的空間會比減少eden的空間更好一些。讓eden空間大小保持恒定,minorgc的頻率不會改變,即使調整survivor空間的大小。

   使用-xx:+printtenuringdistribution選項,對象的總位元組數和目标survivor空間占用可以用來計算survivor空間的大小。重複前面的例子:

   desired survivor size 8388608 bytes, new threshold 1 (max 15) 

   存活對象的總位元組數是1669048,這個并發垃圾回收器(cms)的目标survivor預設使用50%的survivor空間。通過這個資訊,我們可以知道survivor空間至少應該是33380960位元組,大概是32m。這個計算讓我們知道對survivor空間的預估值需要計算對象的歲數更高效以及防止溢出。為了更好的預估survivor的可用空間,你應該監控應用穩定運作情況下的任期分布,并且使用所有的額外總存活對象的位元組數來作為survivor空間的大小。

   在這個例子,為了讓應用計算歲數更加有效,survivor空間需要至少提升32m。前面使用的選項是:

一步步優化JVM五:優化延遲或者響應時間

-xmx1536m -xms1536m -xmn512m -xx:survivorratio=30  

   那麼為了保持minorgc的頻率不發生變化,然後增加survivor空間的大小到32m,那麼修改後的選項如下:

一步步優化JVM五:優化延遲或者響應時間

-xmx1568m -xms1568m -xmn544m -xx:survivvorratio=15  

   當時young代空間增加了,eden空間的大小保持大概相同,且survivor的空間大小增減了。需要注意的時候,-xmx、-xms、-xmn都增加了32m。另外,-xx:survivvorratio=15讓每一個survivor空間的大小都是32m (544/(15+2) = 32)。

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

一步步優化JVM五:優化延遲或者響應時間

-xms1536m -xms1536m -xmn1512m -xx:survivorratio=14  

   再次強調,減少eden空間大小會增加minorgc的頻率。但是,對象會在young代裡面保持更長的時間,由于提升survivor的空間。

   假如運作同樣的應用,我們保持eden的空間不變,增加survivor空間的大小,如下面選項:

一步步優化JVM五:優化延遲或者響應時間

<span style="margin: 0px; padding: 0px; font-size: 14px;"> -xmx1568m -xms1568m -xmn544m -xx:survivorratio=15</span>  

   可以産生如下的任期分布:

   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,但是預期的survivor空間大小是16777216以及任期閥值和最大任期閥值是相等的。這個表明,對象的老化速度是高效的,而且survivor空間沒有溢出。

   在這個例子中,由于歲數超過3的對象很少,你可能像把最大任期閥值設定為3來測試一下,即設定選項-xx:maxtenuringthreshhold=3,那麼整個選項可以設定為:

一步步優化JVM五:優化延遲或者響應時間

-xmx1568m -xms1658m -xmn544m -xx:survivorratio=15 -xx:maxtenuringthreshold=3  

 這個選項設定和之前的選項設定的權衡是,後面這個選擇可以避免在minorgc的時候不必要地把對象從“from” survivor複制到“to”

survivor。在應用運作在穩定狀态的情況下,觀察多次minorgc任期分布情況,看是否有對象最終移動到old代或者顯示的結果還是和前面的結果類似。如果你觀察得到和前面的任期分布情況相同,基本沒有對象的歲數達到15,也沒有survivor的空間溢出,你應該自己設定最大任期閥值以代替jvm預設的15。在這個例子中,沒有長時間存活的對象,由于在他們的歲數沒有到達15的時候就被垃圾回收了。這些對象在minorgc中被回收了,而不是移動到old代裡面。使用并發垃圾回收(cms)的時候,對象從young代移動到old代最終會導緻old的碎片增加,有可能導緻stop-the-world壓縮垃圾回收,這些都是不希望出現的。甯可選擇讓對象在“from”

survivor和“to” survivor中複制,也不要太快的移動到old代。

 你可能需要重複數次監控任期分布、修改survivor空間大小或者重新配置young代的空間大小直到你對應用由于minorgc引起的延遲滿意為止。如果你發現minorgc的時間太長,你可以通過減少young代的大小直到你滿意為止。盡管,減少young代的大小,會導緻更快地移動對象到old代,可能導緻更多的碎片,如果cms的并發垃圾回收能夠跟上對象的轉移率,這種情況就比不能滿足應用的延遲需求更好。如果這步不能滿足應用的minorgc的延遲和頻率需求,這個時候就有必要重新審視需求以及修改應用程式了。

   如果滿足對minorgc延遲的需求,包括延遲時間和延遲頻率,你可以進入下一步,優化cms垃圾回收周期的啟動,下節詳細介紹。

cms垃圾回收器周期   

 一旦young的空間大小(包含eden和survivor空間)已經完善得滿足應用對minorgc産生延遲要求,注意力可以轉移到優化cms垃圾回收器,降低最差延遲時間的時間長度以及最小化最差延遲的頻率。目标是保持可用的old代空間和并發垃圾回收,避免stop-the-world壓縮垃圾回收。

   stop-the-world壓縮垃圾回收是垃圾回收影響延遲的最差情況,對某些應用來說,恐怕無法完全避免開這些,但是本節提供的優化資訊至少可以減少他們的頻率。

 成功的優化cms垃圾回收器需要達到的效果是old代的裡面的垃圾回收的效率要和young代轉移對象到old代的效率相同,沒有能夠完成這個标準可以稱為“比賽失敗”,比賽失敗的結果就是導緻stop-the-world壓縮垃圾回收。不比賽中失敗的一個關鍵是讓下面兩個事情結合起來:1、old代有足夠的空間。2、啟動cms垃圾回收周期開始時機——快到回收對象的速度比較轉移對象來的速度更快。

   cms周期的啟動是基于old代的空間大小的。如果cms周期開始的太晚,他就會輸掉比賽,沒有能夠快速的回收對象以避免溢出old代空間。如果cms周期開始得太早,會造成不必要的壓力以及影響應用的吞吐量。但是,通常來講過早的啟動總比過晚的啟動好。

 hotspot

vm自動地計算出當占用是多少時啟動cms垃圾回收周期。不過在一些場景下,對于避免stop-the-world垃圾回收,他做得并不好。如果觀察到stop-the-world垃圾回收,你可以優化該什麼時候啟動cms周期。在cms垃圾回收中,stop-the-world壓縮垃圾回收在垃圾回收日志中輸出是“concurrent

mode failure”,下面一個例子:

   174.445: [gc 174.446: [parnew: 66408k->66408k(66416k), 0.0000618

   secs]174.446: [cms ( concurrent mode failure): 161928k->162118k(175104k),

   4.0975124 secs] 228336k->162118k(241520k)

   如果你發現有concurrent mode failure你可以通過下面這個選項來控制什麼時候啟動cms垃圾回收:

一步步優化JVM五:優化延遲或者響應時間

-xx:cmsinitiatingoccupancyfraction=<percent>  

   這個值指定了cms垃圾回收時old代的空間占用率該是什麼值。舉例說明,如果你希望old代占用率是65%的時候,啟動cms垃圾回收,你可以設定-xx:cmsinitiatingoccupancyfraction=65。另外一個可以同時使用的選項是

一步步優化JVM五:優化延遲或者響應時間

-xx:+usecmsinitiatingoccupancyonly  

   -xx:+usecmsinitiatingoccupancyonly指定hotspot vm總是使用-xx:cmsinitiatingoccupancyfraction的值作為old的空間使用率限制來啟動cms垃圾回收。如果沒有使用-xx:+usecmsinitiatingoccupancyonly,那麼hotspot vm隻是利用這個值來啟動第一次cms垃圾回收,後面都是使用hotspot vm自動計算出來的值。

   -xx:cmsinitiatingoccupancyfraction=<percent>這個指定的值,應該比垃圾回收之後存活對象的占用率更高,怎麼樣計算存活對象的大小前面在“決定記憶體占用”的章節已經說過了。如果<percent>不比存活對象的占用量大,cms垃圾回收器會一直運作。通常的建議是-xx:cmsinitiatingoccupancyfraction的值應該是存活對象的占用率的1.5倍。舉例說明一下,假如用下面的java堆選項配置:

一步步優化JVM五:優化延遲或者響應時間

-xmx1536m -xms1536m -xmn512m  

 那麼old代的空間大小是1024m(1536-512 =

1024m)。如果存活對象的大小是350m的話,cms垃圾回收周期的啟動閥值應該是old代占用空間是525m,那麼占用率就應該是51%(525/1024=51%),這個隻是初始值,後面還可能根據垃圾回收日志進行修改。那麼修改後的指令行選項是:

一步步優化JVM五:優化延遲或者響應時間

-xmx1536m -xms1536m -xmn512m  -xx:+usecmsinitiatingoccupancyonly   

   -xx:cmsinitiatingoccupancyfraction=51  

 該多早或者多遲啟動cms周期依賴于對象從young代轉移到old代的速率,也就是說,old代空間的增長率。如果old代填充速度比較緩慢,你可以晚一些啟動cms周期,如果填充速度很快,那麼就需要早一點啟動cms周期,但是不能小于存活對象的占用率。如果需要設定得比存活對象的占用率小,應該是增加old代的空間。

   想知道cms周期是開始的太早還是太晚,可以通過評估垃圾回收資訊識别出來。下面是一個cms周期開始得太晚的例子。為了更好閱讀,稍微修改了輸出内容:

[parnew 742993k->648506k(773376k), 0.1688876 secs] [parnew 753466k->659042k(773376k), 0.1695921 secs] [cms-initial-mark 661142k(773376k), 0.0861029 secs] [full gc 645986k->234335k(655360k), 8.9112629 secs] [parnew 339295k->247490k(773376k), 0.0230993 secs] [parnew 352450k->259959k(773376k), 0.1933945 secs]

   注意fullgc在cms-inital-mark之後很快就發生了。cms-initial-mark是報告cms周期多個字段中的一個。下面的例子會使用到更多的字段。

    下面是一個cms開始的太早了的情況:

[parnew 390868k->296358k(773376k), 0.1882258 secs] [cms-initial-mark 298458k(773376k), 0.0847541 secs] [parnew 401318k->306863k(773376k), 0.1933159 secs] [cms-concurrent-mark: 0.787/0.981 secs] [cms-concurrent-preclean: 0.149/0.152 secs] [cms-concurrent-abortable-preclean: 0.105/0.183 secs] [cms-remark 374049k(773376k), 0.0353394 secs] [parnew 407285k->312829k(773376k), 0.1969370 secs] [parnew 405554k->311100k(773376k), 0.1922082 secs] [parnew 404913k->310361k(773376k), 0.1909849 secs] [parnew 406005k->311878k(773376k), 0.2012884 secs] [cms-concurrent-sweep: 2.179/2.963 secs] [cms-concurrent-reset: 0.010/0.010 secs] [parnew 387767k->292925k(773376k), 0.1843175 secs] [cms-initial-mark 295026k(773376k), 0.0865858 secs] [parnew 397885k->303822k(773376k), 0.1995878 secs]

   cms-initial-mark表示cms周期的開始, cms-initial-sweep和cms-concurrent-reset表示周期的結束。注意第一個cms-initial-mark報告堆大小是298458k,然後注意,parnew minorgc報告在cms-initial-mark和cms-concurrent-reset之間隻有很少的占用量變化,堆的占用量可以通過parnew的->的右邊的數值來表示。在這個例子中,cms周期回收了很少的垃圾,通過在cms-initial-mark和cms-concurrent-reset之間隻有很少的占用量變化可看出來。這裡正确的做法是啟動cms周期用更大的old代空間占用率,通過使用參數

-xx:+usecmsinitiatingoccupancyonly和-xx:cmsinitiatingoccupancyfraction=<percent>。基于初始(cms-initial-mark)占用量是298458k以及java堆的大小是773376k,就是cms發生的占用率是35%到40%(298458k/773376k=38.5%),可以使用選項來強制提高占用率的值。

   下面是一個cms周期回收了大量old代空間的例子,而且沒有經曆stop-the-world壓縮垃圾回收,也就沒有并發錯誤(concurrent mode failure)。同樣的修改輸出格式:

[parnew 640710k->546360k(773376k), 0.1839508 secs] [cms-initial-mark 548460k(773376k), 0.0883685 secs] [parnew 651320k->556690k(773376k), 0.2052309 secs] [cms-concurrent-mark: 0.832/1.038 secs] [cms-concurrent-preclean: 0.146/0.151 secs] [cms-concurrent-abortable-preclean: 0.181/0.181 secs] [cms-remark 623877k(773376k), 0.0328863 secs] [parnew 655656k->561336k(773376k), 0.2088224 secs] [parnew 648882k->554390k(773376k), 0.2053158 secs] [parnew 489586k->395012k(773376k), 0.2050494 secs] [parnew 463096k->368901k(773376k), 0.2137257 secs] [cms-concurrent-sweep: 4.873/6.745 secs] [parnew 445124k->350518k(773376k), 0.1800791 secs] [parnew 455478k->361141k(773376k), 0.1849950 secs]

   在這個例子中,在cms周期開始的時候,cms-initial-mark表明占用量是548460k。在cms周期開始和結束(cms-concurrent-reset)之間,parnew minorgc報告顯著的減少了對象的占用量。尤其,在cms-concurrent-sweep之前,占用量從561336k降低到了368901k。這個表明在cms周期中,有190m空間被垃圾回收。需要注意的是,在cms-concurrent-sweep之後的第一個parnew minorgc報告的占用量是350518k。這個說明超過190m被垃圾回收(561336k-350518k=210818k=205.88m)。

   如果你決定優化cms周期的啟動,多嘗試幾個不同的old代占用率。監控垃圾回收資訊以及分析這些資訊可以幫助你做出正确的決定。

強制的垃圾回收

   如果你想要觀察通過調用system.gc()來啟動的fullgc,當使用用cms的時候,有兩種方法來處理這種情況。

   1、你可以請求hotspot vm執行system.gc()的時候使用cms周期,使用如下指令選項:

一步步優化JVM五:優化延遲或者響應時間

-xx:+explicitgcinvokesconcurrent  

  或者  

  -xx:+explicitgcinvokesconcurrentandunloadsclasses  

   第一個選項在java 6及更新版本中能夠使用,第二選項在從java 6 update 4之後才有。如果可以,建議使用後者。

   2、你可以請求hotspot vm選項忽視強制的調用system.gc(),可以使用如下選項:

一步步優化JVM五:優化延遲或者響應時間

-xx:+disableexplicitgc  

   這個選項用來讓其他垃圾回收器忽略system.gc()請求。

   當關閉的強制垃圾回收需要小心,這樣做可能對java性能産生很大的影響,關閉這個功能就像使用system.gc()一樣需要明确的理由。

   在垃圾回收日志裡面找出明确的垃圾回收資訊是非常容易的。垃圾回收的輸出裡面包含了一段文字來說明fullgc是用于調用system.gc().下面是一個例子:

2010-12-16t23:04:39.452-0600: [full gc (system) [cms: 418061k->428608k(16384k), 0.2539726 secs] 418749k->4288608k(31168k), [cms perm : 32428k->32428k(65536k)],0.2540393 secs] [times: user=0.12 sys=0.01, real=0.25 secs]

 注意full

gc後面的(system)标簽,這個說明是system.gc()引起的fullgc。如果你在垃圾回收日志裡面觀察到了明确的fullgc,想想為什麼會出現、是否需要關閉、是否需要把應用源代碼裡面的相關代碼删除掉,對cms垃圾回收周期是否有意義。

并發的permanent代垃圾回收

fullgc發生可能是由于permanent空間滿了引起的,監控fullgc垃圾回收資訊,然後觀察permanent代的占用量,判斷fullgc是否是由于permanent區域滿了引起的。下面是一個由于permanent代滿了引起的fullgc的例子:

2010-12-16t17:14:32.533-0600: [full gc [cms: 95401k->287072k(1048576k), 0.5317934 secs] 482111k->287072k(5190464k), [cms perm : 65534k->58281k(65536k)], 0.5319635 secs] [times: user=0.53 sys=0.00, real=0.53 secs]

    注意permanent代的空間占用量,通過cms perm :标簽識别。permanent代空間大小是括号裡面的值,65536k。在fullgc之前permanent代的占用量是->左邊的值,65534k,fullgc之後的值是58281k。可以看到的是,在fullgc之前,permanent代的占用量以及基本上和permanent代的容量非常接近了,這個說明,fullgc是由permanent代空間溢出導緻的。同樣需要注意的是,old代還沒有到溢出空間的時候,而且沒有證據說明cms周期啟動了。

   hotspot vm預設情況下,cms不會垃圾回收permanent代空間,盡管垃圾回收日志裡面有cms perm标簽。為讓cms回收permanent代的空間,可以用過下面這個指令選項來做到:

一步步優化JVM五:優化延遲或者響應時間

-xx:+cmsclassunloadingenabled  

   如果使用java 6 update 3及之前的版本,你必須指定一個指令選項:

一步步優化JVM五:優化延遲或者響應時間

-xx:+cmspermgensweepingenabled  

   你可以控制permanent的空間占用率來啟動cms permanent代垃圾回收通過下面這個指令選項:

一步步優化JVM五:優化延遲或者響應時間

-xx:cmsinitiatingpermoccupancyfraction=<percent>  

   這個參數的功能和-xx:cmsinitiatingoccupancyfraction很像,他指的是啟動cms周期的permanent代的占用率。這個參數同樣需要和-xx:+cmsclassunloadingenabled配合使用。如果你想一直使用-xx:cmsinitiatingpermoccupancyfraction的值作為啟動cms周期的條件,你必須要指定另外一個選項:

一步步優化JVM五:優化延遲或者響應時間

cms暫停時間優化

 在cms周期裡面,有兩個階段是stop-the-world階段,這個階段所有的應用線程都被阻塞了。這兩階段是“初始标記”階段和“再标記”階段,盡管初始标記解決是單線程的,但是通過不需要花費太長時間,至少比其他垃圾回收的時間短。再标記階段是多線程的,線程數可通過指令選項來控制:

一步步優化JVM五:優化延遲或者響應時間

-xx:parallelgcthreads=<n>  

   在java 6 update 23之後,預設值是通過runtime.availableprocessors()來确定的,不過是建立在傳回值小于等于8的情況下,反之,會使用runtime.availableprocessors()*5/8作為線程數。如果有多個程式運作在同一個機器上面,建議使用比預設線程數更少的線程數。否則,垃圾回收可能會引起其他應用的性能下降,由于在同一個時刻,垃圾回收器使用太多的線程。

   在某些情況下設定下面這個選項可以減少再标記的時間:

一步步優化JVM五:優化延遲或者響應時間

-xx:+cmsscavengebeforeremark  

   這個選項強制hotspot vm在fullgc之前執行minorgc,在再标記步驟之前做minorgc,可以減少再标記的工作量,由于減少了young代的對象數,這些對象能夠在old代擷取到的。

   如果應用有大量的引用或者finalizable對象需要處理,指定下面這個選項可以減少垃圾回收的時間:

一步步優化JVM五:優化延遲或者響應時間

-xx:+parallelrefprocenabled  

   這個選項可以用hotspot vm的任何一種垃圾回收器上,他會是用多個的引用處理線程,而不是單個線程。這個選項不會啟用多線程運作方法的finalizer。他會使用很多線程去發現需要排隊通知的finalizable對象。

下一步

   這一步結束,你需要看看應用的延遲需要是否滿足了,無論是使用throughput垃圾回收器或者并發垃圾回收器。如果沒有能夠滿足應用的需要,那麼回頭看看需求是否合理或者修改應用程式。如果滿足了應用的需求,那麼我們就進入下一步——優化吞吐量。

原文連結:[http://wely.iteye.com/blog/2339048]