本節的目標是作一些優化以知足對應用對延遲的需求。此次須要幾個步驟,包括完善Java堆大小的配置,評估垃圾回收佔用的時間和頻率,也許還要嘗試切換到不一樣的垃圾回收器,以及因爲使用了不一樣的垃圾回收器,須要從新優化Java堆空間大小。
這一步有以下可能的結果:
一、應用的延遲需求被知足了。若是這一步的優化操做知足了應用的延遲需求,你能夠繼續下一步優化(優化吞吐量)。
二、應用的延遲需求未被知足。若是這一步的優化操做未能知足延遲需求,你可能須要從新看看延遲需求是否合理或者修改應用程序。一些可能的問題能夠幫助改善應用的延遲問題:
a、優化Java堆以及修改應用以減小對象的分配和對象的長時間存活。
b、修改JVM的部署結構,讓每個JVM作更少的工做。
上面的兩個步驟均可以減小JVM的對象分配,所以減小垃圾回收的頻率。
這一步從查看垃圾回收對應用的延遲的影響開始,基於前面一節「決定內存消耗」計算出來的Java堆大小。
下面列出了評估垃圾回收對延遲的影響須要進行的幾個事情:
一、
測量
MinorGC的時間。
二、
測量
MinorGC的頻率。
三、
測量
FullGC的時間。
四、
測量
FullGC的頻率。
測量
垃圾回收的時間的和頻率對於改善Java堆大小配置來講是很是重要的。MinorGC的時間和頻率的
測量結果能夠用來改善young代的空間大小。測量最壞狀況下FullGC的時間和頻率能夠用來決定old代的大小,以及是否須要切換成吞吐量垃圾回收器(經過使用-XX:+UseParalleOldGC或者-XX:+UseParallelGC)或者併發垃圾回收器(CMS,經過使用-XX:+UseConcMarkSweepGC)。在使用吞吐量垃圾回收器的時候,若是垃圾回收的延遲和頻率過高以致使應用的延遲需求沒法知足的時候才切換到CMS,若是選擇了切換,須要對CMS垃圾回收器進行優化,後面會詳細介紹這個問題。
接下來詳細介紹前面提到的各類狀況。
需求
下面列舉了幾個這一步優化操做需求,它們來源於應用的系統需求:
一、能夠接收的平均暫停時間。平均暫停時間需求用於和MinorGC消耗的時間比較。
二、
能夠接收的
MinorGC的頻率。其實頻道對於應用負責人來講,沒有平均延遲時間重要。
三、應用負責人可以接受的最大延遲時間。這個時間受到FullGC的影響。
四、應用負責人可以接收的最大延遲的頻率,即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代的空間大小。
上圖是一個展現了MinorGC的例子,這個例子是運行在以下的HotSpot VM命令參數下的。
-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毫秒,比系統的要求高出了很多。上面例子使用的命令選項是
-Xms6144m -Xmx6144m -Xmn2048m -XX:PermSize=96m -XX:MaxPermSize=96m -XX:+UserParallelOldGC
意味着old代的空間大小是4096M,減少young代的空間大小的10%並且要保持old代的空間大小不變,可使用以下選項。
-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。下面是一個示例來講明配置這個:
-Xms8796m -Xmx8796m -Xmn4700m -XX:PermSize=96m -XX:MaxPermSize=96m -XX:+UsePrallelOldGC
注意是-Xms和-Xmx也同步調整了。
另一些調整young代的空間須要注意的事項:
一、old代的空間必定不能小於活動對象的大小的1.5倍。
二、young代的空間至少要有Java堆大小的10%,過小的Java空間會致使過於頻繁的MinorGC。
三、當提升Java堆大小的時候,不要超過JVM可使用的物理內存大小。若是使用過多的物理內存,會致使使用交換區,這個會嚴重影響性能。
若是在僅僅是MinorGC致使了延遲的狀況下,你沒法經過調整young代的空間來知足系統的需求,那麼你須要重 新修改應用程序、修改JVM部署模型把應用部署到多個JVM上面(一般得要多機器了)或者從新評估系統的需求。
若是經過調整MinorGC可以知足應用的延遲需求,接下來就能夠調整old代了,以達到最壞狀況下的延遲和延遲頻率的需求。下一節詳細說明這個問題。
完善old代的大小
這一節的目標是評估因爲FullGC引發的最差暫停時間和頻率。
同前面一個節「完善young代大小」同樣,垃圾回收的統計信息是必須的,在穩定狀態下,FullGC的時間代表了應用最差的延遲,若是發生了多個FullGC,計算多個FullGC的平均消耗時間,更多數據可以更好的評估。
計算兩次不一樣的FullGC之間的時間差,能夠提供出FullGC的頻率,下圖用一個列子來講明兩個FullGC:
若是沒有FullGC,能夠人爲的去幹預,前面說過,可使用VisualVM來觸發FullGC。另外,評估FullGC的頻率須要知道對象的轉移率,這個轉移率說明對象從young代轉移到old代。接下來的介紹如何評估轉移率。
接下有個幾個MinorGC的例子,他們被用來評估FullGC的頻率。
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]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]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]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]
從上面的例子能夠看出:
一、Java堆的大小是6291456K或6144M
二、young代的大小是2097152K或2048M
三、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空間。
兩個survivor空間的中一個被標記爲「from」,另一個標記爲「to」。新的Java對象被分配到Eden空間。好比說,下面的一條語句:
Map<String,String> map = new HashMap<String,String>();
一個新的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空間的大小
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>來指定最大的任期閥值。爲了決定出最大的任期閥值,須要監控任期閥值的分佈和對象歲數的分佈,經過使用下面的選項實現
-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)
- age 1: 16690480 bytes, 16690480 total
存活對象的總字節數是1669048,這個併發垃圾回收器(CMS)的目標survivor默認使用50%的survivor空間。經過這個信息,咱們能夠知道survivor空間至少應該是33380960字節,大概是32M。這個計算讓咱們知道對survivor空間的預估值須要計算對象的歲數更高效以及防止溢出。爲了更好的預估survivor的可用空間,你應該監控應用穩定運行狀況下的任期分佈,而且使用全部的額外總存活對象的字節數來做爲survivor空間的大小。
在這個例子,爲了讓應用計算歲數更加有效,survivor空間須要至少提高32M。前面使用的選項是:
-Xmx1536m -Xms1536m -Xmn512m -XX:SurvivorRatio=30
那麼爲了保持MinorGC的頻率不發生變化,而後增長survivor空間的大小到32M,那麼修改後的選項以下:
-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)。
-Xms1536m -Xms1536m -Xmn1512m -XX:SurvivorRatio=14
再次強調,減小eden空間大小會增長MinorGC的頻率。可是,對象會在young代裏面保持更長的時間,因爲提高survivor的空間。
假如運行一樣的應用,咱們保持eden的空間不變,增長survivor空間的大小,以下面選項:
-Xmx1568m -Xms1568m -Xmn544m -XX:SurvivorRatio=15
能夠產生以下的任期分佈:
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,那麼整個選項能夠設置爲:
-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壓縮垃圾回收。不比賽中失敗的一個關鍵是讓下面兩個事情結合起來:一、old代有足夠的空間。二、啓動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垃圾回收:
-XX:CMSInitiatingOccupancyFraction=<percent>
這個值指定了CMS垃圾回收時old代的空間佔用率該是什麼值。舉例說明,若是你但願old代佔用率是65%的時候,啓動CMS垃圾回收,你能夠設置-XX:CMSInitiatingOccupancyFraction=65。另一個能夠同時使用的選項是
-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堆選項配置:
-Xmx1536m -Xms1536m -Xmn512m
那麼old代的空間大小是1024M(1536-512 = 1024m)。若是存活對象的大小是350M的話,CMS垃圾回收週期的啓動閥值應該是old代佔用空間是525M,那麼佔用率就應該是51%(525/1024=51%),這個只是初始值,後面還可能根據垃圾回收日誌進行修改。那麼修改後的命令行選項是:
-Xmx1536m -Xms1536m -Xmn512m -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=51
該多早或者多遲啓動CMS週期依賴於對象從young代轉移到old代的速率,也就是說,old代空間的增加率。若是old代填充速度比較緩慢,你能夠晚一些啓動CMS週期,若是填充速度很快,那麼就須要早一點啓動CMS週期,可是不能小於存活對象的佔用率。若是須要設置得比存活對象的佔用率小,應該是增長old代的空間。
想知道CMS週期是開始的太早仍是太晚,能夠經過評估垃圾回收信息識別出來。下面是一個CMS週期開始得太晚的例子。爲了更好閱讀,稍微修改了輸出內容:
注意FullGC在CMS-inital-mark以後很快就發生了。CMS-initial-mark是報告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]
下面是一個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][CMS-concurrent-reset: 0.010/0.010 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的時候,有兩種方法來處理這種狀況。
一、你能夠請求HotSpot VM執行System.gc()的時候使用CMS週期,使用以下命令選項:
-XX:+ExplicitGCInvokesConcurrent 或者 -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses
第一個選項在Java 6及更新版本中可以使用,第二選項在從Java 6 Update 4以後纔有。若是能夠,建議使用後者。
二、你能夠請求HotSpot VM選項忽視強制的調用System.gc(),可使用以下選項:
-XX:+DisableExplicitGC
這個選項用來讓其餘垃圾回收器忽略System.gc()請求。
當關閉的強制垃圾回收須要當心,這樣作可能對Java性能產生很大的影響,關閉這個功能就像使用System.gc()同樣須要明確的理由。
在垃圾回收日誌裏面找出明確的垃圾回收信息是很是容易的。垃圾回收的輸出裏面包含了一段文字來講明FullGC是用於調用System.gc().下面是一個例子:
注意Full GC後面的(System)標籤,這個說明是System.gc()引發的FullGC。若是你在垃圾回收日誌裏面觀察到了明確的FullGC,想一想爲何會出現、是否須要關閉、是否須要把應用源代碼裏面的相關代碼刪除掉,對CMS垃圾回收週期是否有意義。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]
併發的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代的空間,能夠用過下面這個命令選項來作到:
-XX:+CMSClassUnloadingEnabled
若是使用Java 6 update 3及以前的版本,你必須指定一個命令選項:
-XX:+CMSPermGenSweepingEnabled
你能夠控制permanent的空間佔用率來啓動CMS permanent代垃圾回收經過下面這個命令選項:
-XX:CMSInitiatingPermOccupancyFraction=<percent>
這個參數的功能和-XX:CMSInitiatingOccupancyFraction很像,他指的是啓動CMS週期的permanent代的佔用率。這個參數一樣須要和-XX:+CMSClassUnloadingEnabled配合使用。若是你想一直使用-XX:CMSInitiatingPermOccupancyFraction的值做爲啓動CMS週期的條件,你必需要指定另一個選項:
-XX:+UseCMSInitiatingOccupancyOnly
CMS暫停時間優化
在CMS週期裏面,有兩個階段是stop-the-world階段,這個階段全部的應用線程都被阻塞了。這兩階段是「初始標記」階段和「再標記」階段,儘管初始標記解決是單線程的,可是經過不須要花費太長時間,至少比其餘垃圾回收的時間短。再標記階段是多線程的,線程數可經過命令選項來控制:
-XX:ParallelGCThreads=<n>
在Java 6 update 23以後,默認值是經過Runtime.availableProcessors()來肯定的,不過是創建在返回值小於等於8的狀況下,反之,會使用Runtime.availableProcessors()*5/8做爲線程數。若是有多個程序運行在同一個機器上面,建議使用比默認線程數更少的線程數。不然,垃圾回收可能會引發其餘應用的性能降低,因爲在同一個時刻,垃圾回收器使用太多的線程。
在某些狀況下設置下面這個選項能夠減小再標記的時間:
-XX:+CMSScavengeBeforeRemark
這個選項強制HotSpot VM在FullGC以前執行MinorGC,在再標記步驟以前作MinorGC,能夠減小再標記的工做量,因爲減小了young代的對象數,這些對象可以在old代獲取到的。
若是應用有大量的引用或者finalizable對象須要處理,指定下面這個選項能夠減小垃圾回收的時間:
-XX:+ParallelRefProcEnabled
這個選項能夠用HotSpot VM的任何一種垃圾回收器上,他會是用多個的引用處理線程,而不是單個線程。這個選項不會啓用多線程運行方法的finalizer。他會使用不少線程去發現須要排隊通知的finalizable對象。
下一步
這一步結束,你須要看看應用的延遲須要是否知足了,不管是使用throughput垃圾回收器或者併發垃圾回收器。若是沒有可以知足應用的須要,那麼回頭看看需求是否合理或者修改應用程序。若是知足了應用的需求,那麼咱們就進入下一步——優化吞吐量。