JVM GC算法 CMS 詳解(轉)

前言

CMS,全稱Concurrent Low Pause Collector,是jdk1.4後期版本開始引入的新gc算法,在jdk5和jdk6中獲得了進一步改進,它的主要適合場景是對響應時間的重要性需求 大於對吞吐量的要求,可以承受垃圾回收線程和應用線程共享處理器資源,而且應用中存在比較多的長生命週期的對象的應用。CMS是用於對tenured generation的回收,也就是年老代的回收,目標是儘可能減小應用的暫停時間,減小full gc發生的概率,利用和應用程序線程併發的垃圾回收線程來標記清除年老代。在咱們的應用中,由於有緩存的存在,而且對於響應時間也有比較高的要求,所以希 望能嘗試使用CMS來替代默認的server型JVM使用的並行收集器,以便得到更短的垃圾回收的暫停時間,提升程序的響應性。算法

CMS收集週期

CMS並不是沒有暫停,而是用兩次短暫停來替代串行標記整理算法的長暫停,它的收集週期是這樣:緩存

初始標記(CMS-initial-mark) -> 併發標記(CMS-concurrent-mark) -> 從新標記(CMS-remark) -> 併發清除(CMS-concurrent-sweep) ->併發重設狀態等待下次CMS的觸發(CMS-concurrent-reset)。 其中的1,3兩個步驟須要暫停全部的應用程序線程的。第一次暫停從root對象開始標記存活的對象,這個階段稱爲初始標記;第二次暫停是在併發標記以後, 暫停全部應用程序線程,從新標記併發標記階段遺漏的對象(在併發標記階段結束後對象狀態的更新致使)。第一次暫停會比較短,第二次暫停一般會比較長,而且 remark這個階段能夠並行標記。多線程

而併發標記、併發清除、併發重設階段的所謂併發,是指一個或者多個垃圾回收線程和應用程序線程併發地運行,垃圾回收線程不會暫停應用程序的執行,若是你有多於一個處理器,那麼併發收集線程將與應用線程在不一樣的處理器上運行,顯然,這樣的開銷就是會下降應用的吞吐量。Remark階段的並行,是指暫停了全部應用程序後,啓動必定數目的垃圾回收進程進行並行標記,此時的應用線程是暫停的。併發

CMS的young generation的回收採用的仍然是並行複製收集器,這個跟Paralle gc算法是一致的。性能

清理過程

3.1 初始標記階段須要STW。

該階段進行可達性分析,標記GC ROOT能直接關聯到的對象。 注意是直接關聯間接關聯的對象在下一階段標記。線程

3.2 併發標記階段是和用戶線程併發執行的過程。

該階段進行GC ROOT TRACING,在第一個階段被暫停的線程從新開始運行。日誌

由前階段標記過的對象出發,全部可到達的對象都在本階段中標記。code

3.3 併發預處理階段作的工做仍是標記,與3.4的重標記功能類似。

既然類似爲何要有這一步?server

前面咱們講過,CMS是以獲取最短停頓時間爲目的的GC。對象

重標記須要STW(Stop The World),所以重標記的工做盡量多的在併發階段完成來減小STW的時間。

此階段標記重新生代晉升的對象、新分配到老年代的對象以及在併發階段被修改了的對象。

3.4 重標記(STW) 暫停全部用戶線程,從新掃描堆中的對象,進行可達性分析,標記活着的對象。

有了前面的基礎,這個階段的工做量被大大減輕,停頓時間所以也會減小。

注意這個階段是多線程的。

3.5 併發清理。用戶線程被從新激活,同時清理那些無效的對象。

3.6 重置。 CMS清除內部狀態,爲下次回收作準備。

參數介紹

一、啓用CMS:-XX:+UseConcMarkSweepGC。 2。CMS默認啓動的回收線程數目是 (ParallelGCThreads + 3)/4) ,若是你須要明確設定,能夠經過-XX:ParallelCMSThreads=20來設定,其中ParallelGCThreads是年輕代的並行收集線程數 三、CMS是不會整理堆碎片的,所以爲了防止堆碎片引發full gc,經過會開啓CMS階段進行合併碎片選項:-XX:+UseCMSCompactAtFullCollection,開啓這個選項必定程度上會影響性能,阿寶的blog裏說也許能夠經過配置適當的CMSFullGCsBeforeCompaction來調整性能,未實踐。 4.爲了減小第二次暫停的時間,開啓並行remark: -XX:+CMSParallelRemarkEnabled。若是remark仍是過長的話,能夠開啓-XX:+CMSScavengeBeforeRemark選項,強制remark以前開始一次minor gc,減小remark的暫停時間,可是在remark以後也將當即開始又一次minor gc。

5.爲了不Perm區滿引發的full gc,建議開啓CMS回收Perm區選項: +CMSPermGenSweepingEnabled -XX:+CMSClassUnloadingEnabled

6.默認CMS是在tenured generation沾滿68%的時候開始進行CMS收集,若是你的年老代增加不是那麼快,而且但願下降CMS次數的話,能夠適當調高此值: -XX:CMSInitiatingOccupancyFraction=80

這裏修改爲80%沾滿的時候纔開始CMS回收。

7.年輕代的並行收集線程數默認是(cpu <= 8) ? cpu : 3 + ((cpu * 5) / 8),若是你但願下降這個線程數,能夠經過-XX:ParallelGCThreads= N 來調整。

8.進入重點,在初步設置了一些參數後,例如:

-server -Xms1536m -Xmx1536m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:PermSize=64m -XX:MaxPermSize=64m -XX:-UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSInitiatingOccupancyFraction=80 -XX:+CMSParallelRemarkEnabled -XX:SoftRefLRUPolicyMSPerMB=0

須要在生產環境或者壓測環境中測量這些參數下系統的表現,這時候須要打開GC日誌查看具體的信息,所以加上參數:

-verbose:gc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:/home/test/logs/gc.log

在運行至關長一段時間內查看CMS的表現狀況,CMS的日誌輸出相似這樣:

4391.322: [GC [1 CMS-initial-mark: 655374K(1310720K)] 662197K(1546688K), 0.0303050 secs] [Times: user=0.02 sys=0.02, real=0.03 secs] 
4391.352: [CMS-concurrent-mark-start] 
4391.779: [CMS-concurrent-mark: 0.427/0.427 secs] [Times: user=1.24 sys=0.31, real=0.42 secs] 
4391.779: [CMS-concurrent-preclean-start] 
4391.821: [CMS-concurrent-preclean: 0.040/0.042 secs] [Times: user=0.13 sys=0.03, real=0.05 secs] 
4391.821: [CMS-concurrent-abortable-preclean-start] 
4392.511: [CMS-concurrent-abortable-preclean: 0.349/0.690 secs] [Times: user=2.02 sys=0.51, real=0.69 secs] 
4392.516: [GC[YG occupancy: 111001 K (235968 K)]4392.516: [Rescan (parallel) , 0.0309960 secs]4392.547: [weak refs processing, 0.0417710 secs] [1 CMS-remark: 655734K(1310720K)] 766736K(1546688K), 0.0932010 secs] [Times: user=0.17 sys=0.00, real=0.09 secs] 
4392.609: [CMS-concurrent-sweep-start] 
4394.310: [CMS-concurrent-sweep: 1.595/1.701 secs] [Times: user=4.78 sys=1.05, real=1.70 secs] 
4394.310: [CMS-concurrent-reset-start] 
4394.364: [CMS-concurrent-reset: 0.054/0.054 secs] [Times: user=0.14 sys=0.06, real=0.06 secs]

其中能夠看到CMS-initial-mark階段暫停了0.0303050秒,而CMS-remark階段暫停了0.0932010秒,所以兩次暫停的總共時間是0.123506秒,也就是123毫秒左右。兩次短暫停的時間之和在200如下能夠稱爲正常現象。

可是你極可能遇到兩種fail引發full gc:Prommotion failed和Concurrent mode failed。

Prommotion failed的日誌輸出大概是這樣:

[ParNew (promotion failed): 320138K->320138K(353920K), 0.2365970 secs]42576.951: [CMS: 1139969K->1120688K( 
166784K), 9.2214860 secs] 1458785K->1120688K(2520704K), 9.4584090 secs]
相關文章
相關標籤/搜索