JVM實用參數(七)CMS收集器
HotSpot JVM的並發標記清理收集器(CMS收集器)的主要目標就是:低應用停頓時間。該目標對於大多數交互式應用很重要,比如web應用。在我們看一下有關JVM的參數之前,讓我們簡要回顧CMS收集器的操作和使用它時可能出現的主要挑戰。
就像吞吐量收集器(參見本係列的第6部分),CMS收集器處理老年代的對象,然而其操作要複雜得多。吞吐量收集器總是暫停應用程序線程,並且可能是相當長的一段時間,然而這能夠使該算法安全地忽略應用程序。相比之下,CMS收集器被設計成在大多數時間能與應用程序線程並行執行,僅僅會有一點(短暫的)停頓時間。GC與應用程序並行的缺點就是,可能會出現各種同步和數據不一致的問題。為了實現安全且正確的並發執行,CMS收集器的GC周期被分為了好幾個連續的階段。
CMS收集器的過程
CMS收集器的GC周期由6個階段組成。其中4個階段(名字以Concurrent開始的)與實際的應用程序是並發執行的,而其他2個階段需要暫停應用程序線程。
- 初始標記:為了收集應用程序的對象引用需要暫停應用程序線程,該階段完成後,應用程序線程再次啟動。
- 並發標記:從第一階段收集到的對象引用開始,遍曆所有其他的對象引用。
- 並發預清理:改變當運行第二階段時,由應用程序線程產生的對象引用,以更新第二階段的結果。
- 重標記:由於第三階段是並發的,對象引用可能會發生進一步改變。因此,應用程序線程會再一次被暫停以更新這些變化,並且在進行實際的清理之前確保一個正確的對象引用視圖。這一階段十分重要,因為必須避免收集到仍被引用的對象。
- 並發清理:所有不再被應用的對象將從堆裏清除掉。
- 並發重置:收集器做一些收尾的工作,以便下一次GC周期能有一個幹淨的狀態。
一個常見的誤解是,CMS收集器運行是完全與應用程序並發的。我們已經看到,事實並非如此,即使“stop-the-world”階段相對於並發階段的時間很短。
應該指出,盡管CMS收集器為老年代垃圾回收提供了幾乎完全並發的解決方案,然而年輕代仍然通過“stop-the-world”方法來進行收集。對於交互式應用,停頓也是可接受的,背後的原理是年輕帶的垃圾回收時間通常是相當短的。
挑戰
當我們在真實的應用中使用CMS收集器時,我們會麵臨兩個主要的挑戰,可能需要進行調優:
- 堆碎片
- 對象分配率高
堆碎片是有可能的,不像吞吐量收集器,CMS收集器並沒有任何碎片整理的機製。因此,應用程序有可能出現這樣的情形,即使總的堆大小遠沒有耗盡,但卻不能分配對象——僅僅是因為沒有足夠連續的空間完全容納對象。當這種事發生後,並發算法不會幫上任何忙,因此,萬不得已JVM會觸發Full GC。回想一下,Full GC 將運行吞吐量收集器的算法,從而解決碎片問題——但卻暫停了應用程序線程。因此盡管CMS收集器帶來完全的並發性,但仍然有可能發生長時間的“stop-the-world”的風險。這是“設計”,而不能避免的——我們隻能通過調優收集器來它的可能性。想要100%保證避免”stop-the-world”,對於交互式應用是有問題的。
第二個挑戰就是應用的對象分配率高。如果獲取對象實例的頻率高於收集器清除堆裏死對象的頻率,並發算法將再次失敗。從某種程度上說,老年代將沒有足夠的可用空間來容納一個從年輕代提升過來的對象。這種情況被稱為“並發模式失敗”,並且JVM會執行堆碎片整理:觸發Full GC。
當這些情形之一出現在實踐中時(經常會出現在生產係統中),經常被證實是老年代有大量不必要的對象。一個可行的辦法就是增加年輕代的堆大小,以防止年輕代短生命的對象提前進入老年代。另一個辦法就似乎利用分析器,快照運行係統的堆轉儲,並且分析過度的對象分配,找出這些對象,最終減少這些對象的申請。
下麵我看看大多數與CMS收集器調優相關的JVM標誌參數。
-XX:+UseConcMarkSweepGC
該標誌首先是激活CMS收集器。默認HotSpot JVM使用的是並行收集器。
-XX:UseParNewGC
當使用CMS收集器時,該標誌激活年輕代使用多線程並行執行垃圾回收。這令人很驚訝,我們不能簡單在並行收集器中重用-XX:UserParNewGC標誌,因為概念上年輕代用的算法是一樣的。然而,對於CMS收集器,年輕代GC算法和老年代GC算法是不同的,因此年輕代GC有兩種不同的實現,並且是兩個不同的標誌。
注意最新的JVM版本,當使用-XX:+UseConcMarkSweepGC時,-XX:UseParNewGC會自動開啟。因此,如果年輕代的並行GC不想開啟,可以通過設置-XX:-UseParNewGC來關掉。
-XX:+CMSConcurrentMTEnabled
當該標誌被啟用時,並發的CMS階段將以多線程執行(因此,多個GC線程會與所有的應用程序線程並行工作)。該標誌已經默認開啟,如果順序執行更好,這取決於所使用的硬件,多線程執行可以通過-XX:-CMSConcurremntMTEnabled禁用。
-XX:ConcGCThreads
標誌-XX:ConcGCThreads=<value>(早期JVM版本也叫-XX:ParallelCMSThreads)定義並發CMS過程運行時的線程數。比如value=4意味著CMS周期的所有階段都以4個線程來執行。盡管更多的線程會加快並發CMS過程,但其也會帶來額外的同步開銷。因此,對於特定的應用程序,應該通過測試來判斷增加CMS線程數是否真的能夠帶來性能的提升。
如果還標誌未設置,JVM會根據並行收集器中的-XX:ParallelGCThreads參數的值來計算出默認的並行CMS線程數。該公式是ConcGCThreads = (ParallelGCThreads + 3)/4。因此,對於CMS收集器, -XX:ParallelGCThreads標誌不僅影響“stop-the-world”垃圾收集階段,還影響並發階段。
總之,有不少方法可以配置CMS收集器的多線程執行。正是由於這個原因,建議第一次運行CMS收集器時使用其默認設置, 然後如果需要調優再進行測試。隻有在生產係統中測量(或類生產測試係統)發現應用程序的暫停時間的目標沒有達到 , 就可以通過這些標誌應該進行GC調優。
-XX:CMSInitiatingOccupancyFraction
當堆滿之後,並行收集器便開始進行垃圾收集,例如,當沒有足夠的空間來容納新分配或提升的對象。對於CMS收集器,長時間等待是不可取的,因為在並發垃圾收集期間應用持續在運行(並且分配對象)。因此,為了在應用程序使用完內存之前完成垃圾收集周期,CMS收集器要比並行收集器更先啟動。
因為不同的應用會有不同對象分配模式,JVM會收集實際的對象分配(和釋放)的運行時數據,並且分析這些數據,來決定什麼時候啟動一次CMS垃圾收集周期。為了引導這一過程, JVM會在一開始執行CMS周期前作一些線索查找。該線索由 -XX:CMSInitiatingOccupancyFraction=<value>來設置,該值代表老年代堆空間的使用率。比如,value=75意味著第一次CMS垃圾收集會在老年代被占用75%時被觸發。通常CMSInitiatingOccupancyFraction的默認值為68(之前很長時間的經曆來決定的)。
-XX:+UseCMSInitiatingOccupancyOnly
我們用-XX+UseCMSInitiatingOccupancyOnly標誌來命令JVM不基於運行時收集的數據來啟動CMS垃圾收集周期。而是,當該標誌被開啟時,JVM通過CMSInitiatingOccupancyFraction的值進行每一次CMS收集,而不僅僅是第一次。然而,請記住大多數情況下,JVM比我們自己能作出更好的垃圾收集決策。因此,隻有當我們充足的理由(比如測試)並且對應用程序產生的對象的生命周期有深刻的認知時,才應該使用該標誌。
-XX:+CMSClassUnloadingEnabled
相對於並行收集器,CMS收集器默認不會對永久代進行垃圾回收。如果希望對永久代進行垃圾回收,可用設置標誌-XX:+CMSClassUnloadingEnabled。在早期JVM版本中,要求設置額外的標誌-XX:+CMSPermGenSweepingEnabled。注意,即使沒有設置這個標誌,一旦永久代耗盡空間也會嚐試進行垃圾回收,但是收集不會是並行的,而再一次進行Full GC。
-XX:+CMSIncrementalMode
該標誌將開啟CMS收集器的增量模式。增量模式經常暫停CMS過程,以便對應用程序線程作出完全的讓步。因此,收集器將花更長的時間完成整個收集周期。因此,隻有通過測試後發現正常CMS周期對應用程序線程幹擾太大時,才應該使用增量模式。由於現代服務器有足夠的處理器來適應並發的垃圾收集,所以這種情況發生得很少。
-XX:+ExplicitGCInvokesConcurrent and -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses
如今,被廣泛接受的最佳實踐是避免顯式地調用GC(所謂的“係統GC”),即在應用程序中調用system.gc()。然而,這個建議是不管使用的GC算法的,值得一提的是,當使用CMS收集器時,係統GC將是一件很不幸的事,因為它默認會觸發一次Full GC。幸運的是,有一種方式可以改變默認設置。標誌-XX:+ExplicitGCInvokesConcurrent命令JVM無論什麼時候調用係統GC,都執行CMS GC,而不是Full GC。第二個標誌-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses保證當有係統GC調用時,永久代也被包括進CMS垃圾回收的範圍內。因此,通過使用這些標誌,我們可以防止出現意料之外的”stop-the-world”的係統GC。
-XX:+DisableExplicitGC
然而在這個問題上…這是一個很好提到- XX:+ DisableExplicitGC標誌的機會,該標誌將告訴JVM完全忽略係統的GC調用(不管使用的收集器是什麼類型)。對於我而言,該標誌屬於默認的標誌集合中,可以安全地定義在每個JVM上運行,而不需要進一步思考。
最後更新:2017-05-24 09:31:48