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上運行,而不需要進一步思考。
- java
- 設計模式
- 設計模式總覽
- 設計原則
- 工廠方法模式
- 抽象工廠模式
- 單例模式
- 建造者模式
- 原型模式
- 適配器模式
- 裝飾者模式
- 代理模式
- 外觀模式
- 橋接模式
- 組合模式
- 享元模式
- 策略模式
- 模板方法模式
- 觀察者模式
- 迭代子模式
- 責任鏈模式
- 命令模式
- 備忘錄模式
- 狀態模式
- 訪問者模式
- 中介者模式
- 解釋器模式
- 附錄
- JVM相關
- JVM內存結構
- Java虛擬機的內存組成以及堆內存介紹
- Java堆和棧
- 附錄-數據結構的堆棧和內存分配的堆區棧區的區別
- Java內存之Java 堆
- Java內存之虛擬機和內存區域概述
- Java 內存之方法區和運行時常量池
- Java 內存之直接內存(堆外內存)
- JAVA內存模型
- Java內存模型介紹
- 內存模型如何解決緩存一致性問題
- 深入理解Java內存模型——基礎
- 深入理解Java內存模型——重排序
- 深入理解Java內存模型——順序一致性
- 深入理解Java內存模型——volatile
- 深入理解Java內存模型——鎖
- 深入理解Java內存模型——final
- 深入理解Java內存模型——總結
- 內存可見性
- JAVA對象模型
- JVM內存結構 VS Java內存模型 VS Java對象模型
- Java的對象模型
- Java的對象頭
- HotSpot虛擬機
- HotSpot虛擬機對象探秘
- 深入分析Java的編譯原理
- Java虛擬機的鎖優化技術
- 對象和數組并不是都在堆上分配內存的
- 垃圾回收
- JVM內存管理及垃圾回收
- JVM 垃圾回收器工作原理及使用實例介紹
- JVM內存回收理論與實現(對象存活的判定)
- JVM參數及調優
- CMS GC日志分析
- JVM實用參數(一)JVM類型以及編譯器模式
- JVM實用參數(二)參數分類和即時(JIT)編譯器診斷
- JVM實用參數(三)打印所有XX參數及值
- JVM實用參數(四)內存調優
- JVM實用參數(五)新生代垃圾回收
- JVM實用參數(六) 吞吐量收集器
- JVM實用參數(七)CMS收集器
- JVM實用參數(八)GC日志
- Java性能調優原則
- JVM 優化經驗總結
- 面試題整理
- 面試題1
- java日志規約
- Spring安全
- OAtuth2.0簡介
- Spring Session 簡介(一)
- Spring Session 簡介(二)
- Spring Session 簡介(三)
- Spring Security 簡介(一)
- Spring Security 簡介(二)
- Spring Security 簡介(三)
- Spring Security 簡介(四)
- Spring Security 簡介(五)
- Spring Security Oauth2 (一)
- Spring Security Oauth2 (二)
- Spring Security Oauth2 (三)
- SpringBoot
- Shiro
- Shiro和Spring Security對比
- Shiro簡介
- Session、Cookie和Cache
- Web Socket
- Spring WebFlux