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

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垃圾回收:

   
-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週期開始得太晚的例子。爲了更好閱讀,稍微修改了輸出內容:

[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-sweepCMS-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的時候,有兩種方法來處理這種情況。

   1、你可以請求HotSpot VM執行System.gc()的時候使用CMS週期,使用如下命令選項:

 
 -XX:+ExplicitGCInvokesConcurrent
   或者
   -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses


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

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

   
-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代的空間,可以用過下面這個命令選項來做到:

   
-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垃圾回收器或者併發垃圾回收器。如果沒有能夠滿足應用的需要,那麼回頭看看需求是否合理或者修改應用程序。如果滿足了應用的需求,那麼我們就進入下一步——優化吞吐量。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章