-
什么是CMS厦凤?
Concurrent Mark Sweep。
看名字就知道育苟,CMS是一款并發(fā)较鼓、使用標記-清除算法的gc。
CMS是針對老年代進行回收的GC违柏。
-
CMS有什么用博烂?
CMS以獲取最小停頓時間為目的。
在一些對響應(yīng)時間有很高要求的應(yīng)用或網(wǎng)站中漱竖,用戶程序不能有長時間的停頓脖母,CMS 可以用于此場景。
CMS如何執(zhí)行闲孤?
總體來說CMS的執(zhí)行過程可以分為以下幾個階段:
3.1 初始標記(STW)
3.2 并發(fā)標記
3.3 并發(fā)預(yù)清理
3.4 重標記(STW)
3.5 并發(fā)清理
3.6 重置
3.1 初始標記階段需要STW。
該階段進行可達性分析烤礁,標記GC ROOT能直接關(guān)聯(lián)到的對象讼积。
注意是直接關(guān)聯(lián)間接關(guān)聯(lián)的對象在下一階段標記。
3.2 并發(fā)標記階段是和用戶線程并發(fā)執(zhí)行的過程脚仔。
該階段進行GC ROOT TRACING勤众,在第一個階段被暫停的線程重新開始運行。
由前階段標記過的對象出發(fā)鲤脏,所有可到達的對象都在本階段中標記们颜。
3.3 并發(fā)預(yù)處理階段做的工作還是標記吕朵,與3.4的重標記功能相似。
既然相似為什么要有這一步窥突?
前面我們講過努溃,CMS是以獲取最短停頓時間為目的的GC。
重標記需要STW(Stop The World)阻问,因此重標記的工作盡可能多的在并發(fā)階段完成來減少STW的時間梧税。
此階段標記從新生代晉升的對象、新分配到老年代的對象以及在并發(fā)階段被修改了的對象称近。
此階段比較復(fù)雜第队,從初學(xué)者容易忽略或者說不理解的地方拋出一個問題大家思考下:
- 如何確定老年代的對象是活著的?
答案很簡單刨秆,通過GC ROOT TRACING可到達的對象就是活著的凳谦。
繼續(xù)延伸,如果存在以下場景怎么辦:
老年代進行GC時如何確保上圖中Current Obj標記為活著的衡未?
(*確認新生代的對象是活著的也存在相同問題尸执,大家可以思考下,文章后面會給出答案*)
答案是必須掃描新生代來確保眠屎。這也是為什么CMS雖然是老年代的gc剔交,但仍要掃描新生代的原因。(注意初始標記也會掃描新生代)
在CMS日志中我們可以清楚地看到掃描日志:
[GC[YG occupancy: 820 K (6528 K)]
[Rescan (parallel) , 0.0024157 secs]
[weak refs processing, 0.0000143 secs]
[scrub string table, 0.0000258 secs]
[1 CMS-remark: 479379K(515960K)] 480200K(522488K), 0.0025249 secs]
[Times: user=0.01 sys=0.00, real=0.00 secs]
Rescan階段(remark階段的一個子階段)會掃描新生代和老年代中的對象改衩。在日志中可以看到此階段標識為Rescan (parallel)岖常,說明此階段是并行進行的。
(看到這里葫督,如果你心中仍有疑問說明已經(jīng)入門了)
重點來了:全量的掃描新生代和老年代會不會很慢竭鞍?肯定會。
CMS號稱是停頓時間最短的GC橄镜,如此長的停頓時間肯定是不能接受的偎快。
如何解決呢?
你們先思考著洽胶。
必須要有一個能夠快速識別新生代和老年代活著的對象的機制晒夹。
先說新生代。
你應(yīng)該已經(jīng)知道姊氓,新生代垃圾回收完剩下的對象全是活著的丐怯,并且活著的對象很少。
如果在掃描新生代前進行一次Minor GC翔横,情況是不是就變得好很多读跷?
CMS 有兩個參數(shù):CMSScheduleRemarkEdenSizeThreshold、CMSScheduleRemarkEdenPenetration禾唁,默認值分別是2M效览、50%无切。兩個參數(shù)組合起來的意思是預(yù)清理后,eden空間使用超過2M時啟動可中斷的并發(fā)預(yù)清理(CMS-concurrent-abortable-preclean)丐枉,直到eden空間使用率達到50%時中斷哆键,進入remark階段。
如果能在可中止的預(yù)清理階段發(fā)生一次Minor GC,那就萬事大吉矛洞、天下太平了洼哎。
這里有一個小問題,可終止的預(yù)清理要執(zhí)行多長時間來保證發(fā)生一次Minor GC?
答案是沒法保證。道理很簡單沼本,因為垃圾回收是JVM自動調(diào)度的,什么時候進行GC我們控制不了噩峦。
但此階段總有一個執(zhí)行時間吧?是的抽兆。
CMS提供了一個參數(shù)CMSMaxAbortablePrecleanTime 识补,默認為5S。
只要到了5S辫红,不管發(fā)沒發(fā)生Minor GC凭涂,有沒有到CMSScheduleRemardEdenPenetration都會中止此階段,進入remark贴妻。
如果在5S內(nèi)還是沒有執(zhí)行Minor GC怎么辦切油?
CMS提供CMSScavengeBeforeRemark參數(shù),使remark前強制進行一次Minor GC名惩。
這樣做利弊都有澎胡。好的一面是減少了remark階段的停頓時間;壞的一面是Minor GC后緊跟著一個remark pause。如此一來娩鹉,停頓時間也比較久攻谁。
CMS日志如下:
7688.150: [CMS-concurrent-preclean-start]
7688.186: [CMS-concurrent-preclean: 0.034/0.035 secs]
7688.186: [CMS-concurrent-abortable-preclean-start]
7688.465: [GC 7688.465: [ParNew: 1040940K->1464K(1044544K), 0.0165840 secs] 1343593K->304365K(2093120K),
0.0167509 secs]7690.093: [CMS-concurrent-abortable-preclean: 1.012/1.907 secs] 7690.095: [GC[YG occupancy: 522484 K (1044544 K)]
7690.095: [Rescan (parallel) , 0.3665541 secs]7690.462: [weak refs processing, 0.0003850 secs] [1 CMS-remark: 302901K(1048576K)] 825385K(2093120K), 0.3670690 secs]
7688.186啟動了可終止的預(yù)清理,在隨后的三秒內(nèi)啟動了Minor GC弯予,然后進入了Remark階段.
實際上為了減少remark階段的STW時間戚宦,預(yù)清理階段會盡可能多做一些事情來減少remark停頓時間。
remark的rescan階段是多線程的锈嫩,為了便于多線程掃描新生代受楼,預(yù)清理階段會將新生代分塊。
每個塊中存放著多個對象呼寸,這樣remark階段就不需要從頭開始識別每個對象的起始位置那槽。
多個線程的職責(zé)就很明確了,把分塊分配給多個線程等舔,很快就掃描完。
遺憾的是糟趾,這種辦法仍然是建立在發(fā)生了Minor GC的條件下慌植。
如果沒有發(fā)生Minor GC甚牲,top(下一個可以分配的地址空間)以下的所有空間被認為是一個塊(這個塊包含了新生代大部分內(nèi)容)。
這種塊對于remark階段并不會起到多少作用蝶柿,因此并行效率也會降低丈钙。
ok,新生代的機制講完了交汤,下面講講老年代雏赦。
老年代的機制與一個叫CARD TABLE的東西(這個東西其實就是個數(shù)組,數(shù)組中每個位置存的是一個byte)密不可分。
CMS將老年代的空間分成大小為512bytes的塊芙扎,card table中的每個元素對應(yīng)著一個塊星岗。
并發(fā)標記時,如果某個對象的引用發(fā)生了變化戒洼,就標記該對象所在的塊為 dirty card俏橘。
并發(fā)預(yù)清理階段就會重新掃描該塊,將該對象引用的對象標識為可達圈浇。
舉個例子:
并發(fā)標記時對象的狀態(tài):
但隨后current obj的引用發(fā)生了變化:
current obj所在的塊被標記為了dirty card寥掐。
隨后到了pre-cleaning階段,還記得該階段的任務(wù)之一就是標記這些在并發(fā)標記階段被修改了的對象么磷蜀?之后那些通過current obj變得可達的對象也被標記了召耘,變成下面這樣:
同時dirty card標志也被清除。
老年代的機制就是這樣褐隆。
不過card table還有其他作用污它。
還記得前面提到的那個問題么?進行Minor GC時,如果有老年代引用新生代妓灌,怎么識別轨蛤?
(有研究表明,在所有的引用中虫埂,老年代引用新生代這種場景不足1%.**原因大家可以自己分析下****)
當有老年代引用新生代祥山,對應(yīng)的card table被標識為相應(yīng)的值(card table中是一個byte,有八位掉伏,約定好每一位的含義就可區(qū)分哪個是引用新生代缝呕,哪個是并發(fā)標記階段修改過的)。
所以斧散,Minor GC通過掃描card table就可以很快的識別老年代引用新生代供常。
這里點一下,hotspot 虛擬機使用字節(jié)碼解釋器鸡捐、JIT編譯器栈暇、 write barrier維護 card table。
當字節(jié)碼解釋器或者JIT編譯器更新了引用箍镜,就會觸發(fā)write barrier操作card table.
再點一下源祈,由于card table的存在煎源,當老年代空間很大時會發(fā)生什么?(這里大家可以自由發(fā)揮想象)
至此香缺,預(yù)清理階段的工作講完手销。
3.4 重標記(STW) 暫停所有用戶線程,重新掃描堆中的對象图张,進行可達性分析,標記活著的對象锋拖。
有了前面的基礎(chǔ),這個階段的工作量被大大減輕祸轮,停頓時間因此也會減少兽埃。
注意這個階段是多線程的。
3.5 并發(fā)清理倔撞。用戶線程被重新激活讲仰,同時清理那些無效的對象。
3.6 重置痪蝇。 CMS清除內(nèi)部狀態(tài)鄙陡,為下次回收做準備。
CMS執(zhí)行過程講完了躏啰,重點講解了并發(fā)預(yù)清理時的操作及CMS幾個關(guān)鍵參數(shù)趁矾。你們可以消化一下,消化完了可以休息一下给僵,因為事情還沒結(jié)束毫捣。
4. CMS有什么問題?
every coin has two sides ------高中英語作文我經(jīng)常用的一句話帝际。
在我看來蔓同,CMS這三個字母就隱含了問題所在。并發(fā)+標記-清除算法 是問題的來源蹲诀。
先說并發(fā)
4.1并發(fā)意味著多線程搶占CPU資源斑粱,即GC線程與用戶線程搶占CPU。這可能會造成用戶線程執(zhí)行效率下降脯爪。
CMS默認的回收線程數(shù)是(CPU個數(shù)+3)/4则北。這個公式的意思是當CPU大于4個時,保證回收線程占用至少25%的CPU資源,這樣用戶線程占用75%的CPU痕慢,這是可以接受的尚揣。
但是,如果CPU資源很少掖举,比如只有兩個的時候怎么辦快骗?按照上面的公式,CMS會啟動1個GC線程。相當于GC線程占用了50%的CPU資源方篮,這就可能導(dǎo)致用戶程序的執(zhí)行速度忽然降低了50%思灌,50%已經(jīng)是很明顯的降低了。
這種場景怎么處理呢恭取?
我給的答案是可以不用考慮這種場景。現(xiàn)在的PC機中都至少有雙核處理器熄守,更別說大型的服務(wù)器了蜈垮。
CMS的解決方案是提供了一個 incremental mode(增量模式)。
在這種模式下裕照,進行并發(fā)標記攒发、清理時讓GC線程、用戶線程交替運行晋南,盡量減少GC線程獨占CPU資源的時間惠猿。
這會造成GC時間更長,但對用戶線程造成的影響就會少一些负间。
但實踐證明偶妖,這種模式下CMS的表現(xiàn)很一般,并沒有什么大的優(yōu)化政溃。
i-CMS已經(jīng)被聲明為“deprecated”趾访,不再提倡使用。
4.2 并發(fā)清理階段用戶線程還在運行董虱,這段時間就可能產(chǎn)生新的垃圾扼鞋,新的垃圾在此次GC無法清除,只能等到下次清理愤诱。這些垃圾有個專業(yè)名詞:浮動垃圾云头。
由于垃圾回收階段用戶線程仍在執(zhí)行,必需預(yù)留出內(nèi)存空間給用戶線程使用淫半。因此不能像其他回收器那樣溃槐,等到老年代滿了再進行GC。
CMS 提供了CMSInitiatingOccupancyFraction參數(shù)來設(shè)置老年代空間使用百分比,達到百分比就進行垃圾回收撮慨。
這個參數(shù)默認是92%竿痰,參數(shù)選擇需要看具體的應(yīng)用場景。
設(shè)置的太小會導(dǎo)致頻繁的CMS GC砌溺,產(chǎn)生大量的停頓影涉;反過來想,設(shè)置的太高會發(fā)生什么规伐?
假設(shè)現(xiàn)在設(shè)置為99%蟹倾,還剩1%的空間可以使用。
在并發(fā)清理階段,若用戶線程需要使用的空間大于1%鲜棠,就會產(chǎn)生Concurrent Mode Failure錯誤肌厨,意思就是說并發(fā)模式失敗。
這時豁陆,虛擬機就會啟動備案:使用Serial Old收集器重新對老年代進行垃圾回收.如此一來柑爸,停頓時間變得更長。
所以CMSInitiatingOccupancyFraction的設(shè)置要具體問題具體分析盒音。
網(wǎng)上有一些設(shè)置此參數(shù)的公式表鳍,個人認為不是很嚴謹(原因就是CMS另外一個問題導(dǎo)致的),因此不寫出來以免大家疑惑。
其實CMS有動態(tài)檢查機制祥诽。
CMS會根據(jù)歷史記錄譬圣,預(yù)測老年代還需要多久填滿及進行一次回收所需要的時間。
在老年代空間用完之前雄坪,CMS可以根據(jù)自己的預(yù)測自動執(zhí)行垃圾回收厘熟。
這個特性可以使用參數(shù)UseCMSInitiatingOccupancyOnly來關(guān)閉。
這里提個問題給讀者思考维哈,如果讓你設(shè)計绳姨,如何預(yù)測什么時候開始自動執(zhí)行?
4.3 前兩個問題是由并發(fā)引起的笨农,接下來要說的問題就是由標記-清除算法引起的就缆。
使用標記-清除算法可能造成大量的空間碎片≮艘啵空間碎片過多竭宰,就會給大對象分配帶來麻煩。
往往老年代還有很大剩余空間份招,但無法找到足夠大的連續(xù)空間來分配當前對象,不得不觸發(fā)一次Full GC切揭。
CMS的解決方案是使用UseCMSCompactAtFullCollection參數(shù)(默認開啟),在頂不住要進行Full GC時開啟內(nèi)存碎片整理锁摔。
這個過程需要STW廓旬,碎片問題解決了,但停頓時間又變長了。
虛擬機還提供了另外一個參數(shù)CMSFullGCsBeforeCompaction谐腰,用于設(shè)置執(zhí)行多少次不壓縮的Full GC后孕豹,跟著來一次帶壓縮的(默認為0,每次進入Full GC時都進行碎片整理)十气。
延伸一個“foreground collector”的東西給大家励背,這個玩意在Java8中也聲明為deprecated。(https://bugs.openjdk.java.net/browse/JDK-8027132)
CMS存在的問題已經(jīng)講清楚砸西,大家消化下叶眉。
至此址儒,CMS相關(guān)內(nèi)容已經(jīng)講完。
總結(jié)一下:
CMS采用了多種方式盡可能降低GC的暫停時間,減少用戶程序停頓衅疙。
停頓時間降低的同時犧牲了CPU吞吐量 莲趣。
這是在停頓時間和性能間做出的取舍,可以簡單理解為"空間(性能)"換時間饱溢。
轉(zhuǎn)載地址:https://www.cnblogs.com/littleLord/p/5380624.html