原文連接
轉(zhuǎn)載連接
譯者: iDestiny 校對:梁海艦
HotSpot JVM的并發(fā)標記清理收集器(CMS收集器)的主要目標就是:低應(yīng)用停頓時間。該目標對于大多數(shù)交互式應(yīng)用很重要,比如web應(yīng)用评雌。在我們看一下有關(guān)JVM的參數(shù)之前,讓我們簡要回顧CMS收集器的操作和使用它時可能出現(xiàn)的主要挑戰(zhàn)纽乱。
就像吞吐量收集器(參見本系列的第6部分),CMS收集器處理老年代的對象,然而其操作要復(fù)雜得多谷婆。吞吐量收集器總是暫停應(yīng)用程序線程漠烧,并且可能是相當長的一段時間柴我,然而這能夠使該算法安全地忽略應(yīng)用程序堪伍。相比之下锚烦,CMS收集器被設(shè)計成在大多數(shù)時間能與應(yīng)用程序線程并行執(zhí)行,僅僅會有一點(短暫的)停頓時間杠娱。GC與應(yīng)用程序并行的缺點就是挽牢,可能會出現(xiàn)各種同步和數(shù)據(jù)不一致的問題。為了實現(xiàn)安全且正確的并發(fā)執(zhí)行摊求,CMS收集器的GC周期被分為了好幾個連續(xù)的階段禽拔。
CMS收集器的過程
CMS收集器的GC周期由6個階段組成。其中4個階段(名字以Concurrent開始的)與實際的應(yīng)用程序是并發(fā)執(zhí)行的室叉,而其他2個階段需要暫停應(yīng)用程序線程睹栖。
- 初始標記:為了收集應(yīng)用程序的對象引用需要暫停應(yīng)用程序線程,該階段完成后茧痕,應(yīng)用程序線程再次啟動野来。
- 并發(fā)標記:從第一階段收集到的對象引用開始,遍歷所有其他的對象引用踪旷。
- 并發(fā)預(yù)清理:改變當運行第二階段時曼氛,由應(yīng)用程序線程產(chǎn)生的對象引用,以更新第二階段的結(jié)果令野。
- 重標記:由于第三階段是并發(fā)的舀患,對象引用可能會發(fā)生進一步改變。因此气破,應(yīng)用程序線程會再一次被暫停以更新這些變化聊浅,并且在進行實際的清理之前確保一個正確的對象引用視圖。這一階段十分重要现使,因為必須避免收集到仍被引用的對象低匙。
- 并發(fā)清理:所有不再被應(yīng)用的對象將從堆里清除掉。
- 并發(fā)重置:收集器做一些收尾的工作碳锈,以便下一次GC周期能有一個干凈的狀態(tài)顽冶。
一個常見的誤解是,CMS收集器運行是完全與應(yīng)用程序并發(fā)的。我們已經(jīng)看到售碳,事實并非如此渗稍,即使“stop-the-world”階段相對于并發(fā)階段的時間很短佩迟。
應(yīng)該指出,盡管CMS收集器為老年代垃圾回收提供了幾乎完全并發(fā)的解決方案竿屹,然而年輕代仍然通過“stop-the-world”方法來進行收集报强。對于交互式應(yīng)用,停頓也是可接受的拱燃,背后的原理是年輕帶的垃圾回收時間通常是相當短的秉溉。
挑戰(zhàn)
當我們在真實的應(yīng)用中使用CMS收集器時,我們會面臨兩個主要的挑戰(zhàn)碗誉,可能需要進行調(diào)優(yōu):
- 堆碎片
- 對象分配率高
堆碎片是有可能的召嘶,不像吞吐量收集器,CMS收集器并沒有任何碎片整理的機制哮缺。因此弄跌,應(yīng)用程序有可能出現(xiàn)這樣的情形,即使總的堆大小遠沒有耗盡尝苇,但卻不能分配對象——僅僅是因為沒有足夠連續(xù)的空間完全容納對象铛只。當這種事發(fā)生后,并發(fā)算法不會幫上任何忙糠溜,因此淳玩,萬不得已JVM會觸發(fā)Full GC》歉停回想一下蜕着,F(xiàn)ull GC 將運行吞吐量收集器的算法,從而解決碎片問題——但卻暫停了應(yīng)用程序線程红柱。因此盡管CMS收集器帶來完全的并發(fā)性承匣,但仍然有可能發(fā)生長時間的“stop-the-world”的風(fēng)險。這是“設(shè)計”锤悄,而不能避免的——我們只能通過調(diào)優(yōu)收集器來它的可能性韧骗。想要100%保證避免”stop-the-world”,對于交互式應(yīng)用是有問題的铁蹈。
第二個挑戰(zhàn)就是應(yīng)用的對象分配率高宽闲。如果獲取對象實例的頻率高于收集器清除堆里死對象的頻率众眨,并發(fā)算法將再次失敗握牧。從某種程度上說,老年代將沒有足夠的可用空間來容納一個從年輕代提升過來的對象娩梨。這種情況被稱為“并發(fā)模式失敗”沿腰,并且JVM會執(zhí)行堆碎片整理:觸發(fā)Full GC。
當這些情形之一出現(xiàn)在實踐中時(經(jīng)常會出現(xiàn)在生產(chǎn)系統(tǒng)中)狈定,經(jīng)常被證實是老年代有大量不必要的對象颂龙。一個可行的辦法就是增加年輕代的堆大小习蓬,以防止年輕代短生命的對象提前進入老年代。另一個辦法就似乎利用分析器措嵌,快照運行系統(tǒng)的堆轉(zhuǎn)儲躲叼,并且分析過度的對象分配,找出這些對象企巢,最終減少這些對象的申請枫慷。
下面我看看大多數(shù)與CMS收集器調(diào)優(yōu)相關(guān)的JVM標志參數(shù):
-XX:+UseConcMarkSweepGC
該標志首先是激活CMS收集器。默認HotSpot JVM使用的是并行收集器浪规。
-XX:UseParNewGC
當使用CMS收集器時或听,該標志激活年輕代使用多線程并行執(zhí)行垃圾回收。這令人很驚訝笋婿,我們不能簡單在并行收集器中重用-XX:UserParNewGC標志誉裆,因為概念上年輕代用的算法是一樣的。然而缸濒,對于CMS收集器足丢,年輕代GC算法和老年代GC算法是不同的,因此年輕代GC有兩種不同的實現(xiàn)绍填,并且是兩個不同的標志霎桅。
注意最新的JVM版本,當使用-XX:+UseConcMarkSweepGC時讨永,-XX:UseParNewGC會自動開啟滔驶。因此,如果年輕代的并行GC不想開啟卿闹,可以通過設(shè)置-XX:-UseParNewGC來關(guān)掉揭糕。
-XX:+CMSConcurrentMTEnabled
當該標志被啟用時,并發(fā)的CMS階段將以多線程執(zhí)行(因此锻霎,多個GC線程會與所有的應(yīng)用程序線程并行工作)著角。該標志已經(jīng)默認開啟,如果順序執(zhí)行更好旋恼,這取決于所使用的硬件吏口,多線程執(zhí)行可以通過-XX:-CMSConcurremntMTEnabled禁用。
-XX:ConcGCThreads
標志-XX:ConcGCThreads=<value>(早期JVM版本也叫-XX:ParallelCMSThreads)定義并發(fā)CMS過程運行時的線程數(shù)冰更。比如value=4意味著CMS周期的所有階段都以4個線程來執(zhí)行产徊。盡管更多的線程會加快并發(fā)CMS過程,但其也會帶來額外的同步開銷蜀细。因此舟铜,對于特定的應(yīng)用程序,應(yīng)該通過測試來判斷增加CMS線程數(shù)是否真的能夠帶來性能的提升奠衔。
如果還標志未設(shè)置谆刨,JVM會根據(jù)并行收集器中的-XX:ParallelGCThreads參數(shù)的值來計算出默認的并行CMS線程數(shù)塘娶。該公式是ConcGCThreads = (ParallelGCThreads + 3)/4。因此痊夭,對于CMS收集器刁岸, -XX:ParallelGCThreads標志不僅影響“stop-the-world”垃圾收集階段,還影響并發(fā)階段她我。
總之难捌,有不少方法可以配置CMS收集器的多線程執(zhí)行。正是由于這個原因,建議第一次運行CMS收集器時使用其默認設(shè)置, 然后如果需要調(diào)優(yōu)再進行測試鸦难。只有在生產(chǎn)系統(tǒng)中測量(或類生產(chǎn)測試系統(tǒng))發(fā)現(xiàn)應(yīng)用程序的暫停時間的目標沒有達到 , 就可以通過這些標志應(yīng)該進行GC調(diào)優(yōu)根吁。
-XX:CMSInitiatingOccupancyFraction
當堆滿之后,并行收集器便開始進行垃圾收集合蔽,例如击敌,當沒有足夠的空間來容納新分配或提升的對象。對于CMS收集器拴事,長時間等待是不可取的沃斤,因為在并發(fā)垃圾收集期間應(yīng)用持續(xù)在運行(并且分配對象)。因此刃宵,為了在應(yīng)用程序使用完內(nèi)存之前完成垃圾收集周期衡瓶,CMS收集器要比并行收集器更先啟動。
因為不同的應(yīng)用會有不同對象分配模式牲证,JVM會收集實際的對象分配(和釋放)的運行時數(shù)據(jù)哮针,并且分析這些數(shù)據(jù),來決定什么時候啟動一次CMS垃圾收集周期坦袍。為了引導(dǎo)這一過程十厢, JVM會在一開始執(zhí)行CMS周期前作一些線索查找。該線索由 -XX:CMSInitiatingOccupancyFraction=<value>來設(shè)置捂齐,該值代表老年代堆空間的使用率蛮放。比如,value=75意味著第一次CMS垃圾收集會在老年代被占用75%時被觸發(fā)奠宜。通常CMSInitiatingOccupancyFraction的默認值為68(之前很長時間的經(jīng)歷來決定的)包颁。
-XX:+UseCMSInitiatingOccupancyOnly
我們用-XX+UseCMSInitiatingOccupancyOnly標志來命令JVM不基于運行時收集的數(shù)據(jù)來啟動CMS垃圾收集周期。而是压真,當該標志被開啟時娩嚼,JVM通過CMSInitiatingOccupancyFraction的值進行每一次CMS收集,而不僅僅是第一次榴都。然而待锈,請記住大多數(shù)情況下漠其,JVM比我們自己能作出更好的垃圾收集決策嘴高。因此竿音,只有當我們充足的理由(比如測試)并且對應(yīng)用程序產(chǎn)生的對象的生命周期有深刻的認知時,才應(yīng)該使用該標志拴驮。
-XX:+CMSClassUnloadingEnabled
相對于并行收集器春瞬,CMS收集器默認不會對永久代進行垃圾回收。如果希望對永久代進行垃圾回收套啤,可用設(shè)置標志-XX:+CMSClassUnloadingEnabled宽气。在早期JVM版本中,要求設(shè)置額外的標志-XX:+CMSPermGenSweepingEnabled潜沦。注意萄涯,即使沒有設(shè)置這個標志,一旦永久代耗盡空間也會嘗試進行垃圾回收唆鸡,但是收集不會是并行的涝影,而再一次進行Full GC。
-XX:+CMSIncrementalMode
該標志將開啟CMS收集器的增量模式争占。增量模式經(jīng)常暫停CMS過程燃逻,以便對應(yīng)用程序線程作出完全的讓步。因此臂痕,收集器將花更長的時間完成整個收集周期伯襟。因此,只有通過測試后發(fā)現(xiàn)正常CMS周期對應(yīng)用程序線程干擾太大時握童,才應(yīng)該使用增量模式姆怪。由于現(xiàn)代服務(wù)器有足夠的處理器來適應(yīng)并發(fā)的垃圾收集,所以這種情況發(fā)生得很少澡绩。
-XX:+ExplicitGCInvokesConcurrent
and
-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses
如今,被廣泛接受的最佳實踐是避免顯式地調(diào)用GC(所謂的“系統(tǒng)GC”)片效,即在應(yīng)用程序中調(diào)用system.gc()。然而英古,這個建議是不管使用的GC算法的淀衣,值得一提的是,當使用CMS收集器時召调,系統(tǒng)GC將是一件很不幸的事膨桥,因為它默認會觸發(fā)一次Full GC。幸運的是唠叛,有一種方式可以改變默認設(shè)置只嚣。標志-XX:+ExplicitGCInvokesConcurrent命令JVM無論什么時候調(diào)用系統(tǒng)GC,都執(zhí)行CMS GC艺沼,而不是Full GC册舞。第二個標志-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses保證當有系統(tǒng)GC調(diào)用時,永久代也被包括進CMS垃圾回收的范圍內(nèi)障般。因此调鲸,通過使用這些標志盛杰,我們可以防止出現(xiàn)意料之外的”stop-the-world”的系統(tǒng)GC。
-XX:+DisableExplicitGC
然而在這個問題上…這是一個很好提到- XX:+ DisableExplicitGC標志的機會藐石,該標志將告訴JVM完全忽略系統(tǒng)的GC調(diào)用(不管使用的收集器是什么類型)即供。對于我而言,該標志屬于默認的標志集合中于微,可以安全地定義在每個JVM上運行逗嫡,而不需要進一步思考。
轉(zhuǎn)載自
并發(fā)編程網(wǎng) – ifeve.com
轉(zhuǎn)載鏈接地址:
JVM實用參數(shù)(七)CMS收集器