JVM調優:選擇合適的GC collector (三)

原文地址:http://blog.csdn.net/historyasamirror/article/details/6245157

CMS Collector 

在很多地方,CMS Collector常被翻譯成“併發”,而ParallelGC被稱爲“並行”,但中文裏,這兩詞的區分度並不明顯。事實上,所謂的Parallel是指,在執行GC的時候將會有多個GC線程共同工作,但是,在執行GC的過程中仍然是“stop-the-world”。CMS的區別在於,在執行GC的時候,GC線程是不需要暫停application的線程,而是和它們“併發”一起工作。
所以,採用CMS的原因就在於它可以提供最低的pause time。

回到CMS的示意圖:

 

這張圖表示的是CMS在執行Full GC的過程,這個過程包括了6個步驟:
# STW initial mark
# Concurrent marking
# Concurrent precleaning
# STW remark
# Concurrent sweeping
# Concurrent reset

STW表示的意思就是“stop-the-world”。
所以,CMS也並不是完全不會暫停application的,在這六個步驟中,有兩個步驟需要STW,分別是:initial mark和remark(如圖所示)。而其它的四個步驟是可以和application“併發”執行。initial mark是由一個GC thread來執行,它的暫停時間相對比較短。而remark過程的暫停時間要比initial mark更長,且通常由多個thread執行。
這六個步驟的具體內容我就不寫了(其實俺也似懂非懂),有興趣的可以參考【1】,【2】。

 

接下來看看實驗結果。


實驗結果

 

JVM參數如下:

java -jar -Xms10g -Xmx15g -XX:+UseConcMarkSweepGC -XX:NewSize=6g -XX:MaxNewSize=6g -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps  -Xloggc:./log/gc.log Slaver.jar

 

 

從圖中可以看出,採用CMS Collector的最大的不同在於它已經沒有了那個“大峽谷”,意味着發生的Full GC並沒有導致系統的throughput降低到0。雖然圖中也有幾次曲線的下降(事實上,這就是發生Full GC的地方),但是曲線的下降很微弱,並且,持續時間也不太長。
整體而言,系統的平均throughput大概在7000 – 6000 request/sec之間,要比Serial GC好,但略低於Parallel GC。

看一個Minor GC的log:
519.514: [GC 519.514: [ParNew: 5149852K->83183K(5662336K), 0.0831770 secs] 6955196K->1905793K(9856640K), 0.0833560 secs] [Times: user=0.57 sys=0.03, real=0.08 secs ] 
採用CMS GC在發生Minor GC的時候採用的collector類似於Parallel GC,log也和Parallel GC的log類似。不多解釋。

 

重點在於Full GC的log:
2051.800: [GC [1 CMS-initial-mark : 6040466K(6555784K)] 6161554K(12218120K), 0.1028810 secs] [Times: user=0.10 sys=0.00, real=0.11 secs ] 
2051.903: [CMS-concurrent-mark-start ]
2059.492: [GC 2059.492: [ParNew: 5153779K->129958K(5662336K), 0.1145560 secs] 11194245K->6189004K(12218120K), 0.1147330 secs] [Times: user=0.82 sys=0.04, real=0.11 secs] 
2067.229: [GC 2067.229: [ParNew: 5163174K->92868K(5662336K), 0.1136260 secs] 11222220K->6170498K(12218120K), 0.1137820 secs] [Times: user=0.82 sys=0.00, real=0.12 secs] 
2075.005: [GC 2075.005: [ParNew: 5126084K->126301K(5662336K), 0.1205450 secs] 11203714K->6222479K(12218120K), 0.1207120 secs] [Times: user=0.84 sys=0.01, real=0.12 secs] 
2077.487: [CMS-concurrent-mark: 25.231/25.584 secs] [Times: user=158.91 sys=22.71, real=25.58 secs ] 
2077.487: [CMS-concurrent-preclean-start ]
2078.512: [CMS-concurrent-preclean: 0.961/1.025 secs] [Times: user=5.97 sys=1.20, real=1.03 secs] 
2078.513: [CMS-concurrent-abortable-preclean-start]
2082.466: [GC 2082.467: [ParNew: 5159517K->89444K(5662336K), 0.1162740 secs] 11255695K->6204092K(12218120K), 0.1164340 secs] [Times: user=0.82 sys=0.01, real=0.12 secs] 
 CMS: abort preclean due to time 2083.642: [CMS-concurrent-abortable-preclean: 4.933/5.129 secs] [Times: user=31.10 sys=4.89, real=5.12 secs] 
2083.644: [GC[YG occupancy: 877128 K (5662336 K)]2083.644: [Rescan (parallel) , 0.5058390 secs]2084.150: [weak refs processing, 0.0000630 secs] [1 CMS-remark: 6114647K(6555784K)] 6991776K(12218120K), 0.5060260 secs] [Times: user=3.35 sys=0.01, real=0.50 secs ] 
2084.150: [CMS-concurrent-sweep-start ]
2090.416: [GC 2090.416: [ParNew: 5122660K->124614K(5662336K), 0.1247190 secs] 11237258K->6257803K(12218120K), 0.1248800 secs] [Times: user=0.88 sys=0.00, real=0.12 secs] 
2095.868: [CMS-concurrent-sweep: 11.593/11.718 secs] [Times: user=70.11 sys=11.53, real=11.72 secs] 
2095.896: [CMS-concurrent-reset-start ]
2096.124: [CMS-concurrent-reset: 0.227/0.227 secs] [Times: user=1.33 sys=0.19, real=0.23 secs]
 

Full GC的log和其它的collector完全不同,簡單解釋一下:
log的第一行CMS-initial-mark 表示CMS執行它的第一步:initial mark。它花費的時間是real=0.11 secs,由於這一步驟是STW的,所以整個application被暫停了0.11秒。並且,user time和real time相差不大,所以確實是只有一個thread執行這一步;
CMS-concurrent-mark-start 表示開始執行第二步驟:concurrent marking。它執行時間是real=25.58 secs,但因爲這一步是可以併發執行的,所以系統在這段時間內並沒有暫停;
CMS-concurrent-preclean-start 表示執行第三步驟:concurrent precleaning。同樣的,這一步也是併發執行;
最重要的是這一條語句:
2083.644: [GC[YG occupancy: 877128 K (5662336 K)]2083.644: [Rescan (parallel) , 0.5058390 secs]2084.150: [weak refs processing, 0.0000630 secs] [1 CMS-remark: 6114647K(6555784K)] 6991776K(12218120K), 0.5060260 secs] [Times: user=3.35 sys=0.01, real=0.50 secs] 
這一步是步驟:remark的執行結果,它的執行時間是real=0.50 secs。因爲這是STW的步驟,並且它的pause time一般是最長的,所以這一步的執行時間會直接決定這次Full GC對系統的影響。這次只執行了0.5秒,系統的throughput未受太大影響。
後面的log分別記錄了concurrent-sweep和concurrent-reset。就不多說了,更詳細的log分析可見【3】。

 

結論 

比較了這幾種Collector,發現CMS應該是最適合我的系統的。因爲它並不會因爲Full GC而在未來的某一時刻突然停滯工作。這一點其實在很多系統中都是非常重要的,比如Web Server ....

 

多說一點 

貼另外兩張實驗結果圖:

 

 

這兩張圖也是採用CMS Collector實驗得到的。區別在於使用了不同的參數。第一張圖是CMS Collector採用了incremental model的方式:
java -jar -Xms10g -Xmx15g -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:NewSize=6g -XX:MaxNewSize=6g -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -Xloggc:./log/gc.log Slaver.jar 

而第二張圖則調整了AbortablePrecleanTime的值:
java -jar -Xms10g -Xmx15g -XX:+UseConcMarkSweepGC -XX:CMSMaxAbortablePrecleanTime=15   -XX:NewSize=6g -XX:MaxNewSize=6g -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps  -Xloggc:./log/gc.log Slaver.jar 

這兩個參數有什麼用不多解釋,只是想說明,即便選擇了合適的Collector,也可能由於其它參數的設置而產生巨大差異。

JVM的調優確實不簡單。這個並沒有絕對的準則或者公式,唯一的好辦法就是實驗。

( The End ) 

 

Reference: 

 

【1】Did You Know ... 
【2】Java SE 6 HotSpot[tm] Virtual Machine Garbage Collection Tuning 
【3】Understanding CMS GC Logs 


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