一、新生代垃圾回收器的比較:
二、老年代垃圾回收器的比較:
三、CMS垃圾收集器
1、CMS(Concurrent Mark Sweep) 收集器是一種以獲取最短回收停頓時間為目標的收集器,適用于集中在互聯網站或者
B/S系統的服務端的Java應用。
2、CMS收集器是基于"標記-清除"算法實現的,可以跟新生代的parallel New、Serial搭配使用。
四、CMS的特性
1、CMS只會回收老年代和永久代的垃圾,不會收集年輕代;
2、CMS是一種預處理垃圾回收器,它不能等到old內存用盡時回收,需要在內存用盡之前,完成回收操作,否則會導致并發回收失敗,所以CMS垃圾回收器開始執行回收操作,有一個觸發閾值,JDK6之前的默認閾值是68%, 而JDK6及以上版本是92%;
3、CMS不會移動對象以保證空閑空間的連續性,相反,CMS保存所有空閑內存片段的列表,通過這樣的方式,CMS可以避免為存活對象重新分配位置引起的開銷,但是相應的會引起內存的碎片化。
五、CMS的執行步驟
1、初始標記:
標記老年代中所有的GC Roots對象;
標記年輕代中活著的對象引用到的老年代對象(指的是年輕代中還存活的引用類型對象,引用指向老年代中的對象)
2、并發標記
因為是并發運行的,在運行期間會發生新生代的對象晉升到老年代、或者是直接在老年代分配對象、或者更新老年代對象的引用關系等等,對于這些對象,都是需要進行重新標記的,否則有些對象就會被遺漏,發生漏標的情況。為了提高重新標記的效率,該階段會把上述對象所在的Card標識為Dirty,后續只需掃描這些Dirty Card的對象,避免掃描整個老年代。
3、預清理階段
前一個階段已經說明,不能標記出老年代全部的存活的對象,是因為標記的同時應用程序會改變一些對象引用,這個階段就是用來處理前一個階段因為引用關系改變導致沒有標記到的存活對象,它會掃描所有標記的Dirty Card。
4、重新標記
這個階段會導致第二次stop the word,該階段的任務是完成標記整個老年代的所有存活對象。
這個階段,重新標記的內存范圍是整個堆,包含 young_gen和old_gen。為什么要掃描新生代呢,因為對于老年代中的對象,如果被新生代中的對象引用,那么就會被視為存活對象,即使新生代的對象已經不可達了,也會使用這些不可達的對象當做CMS的"GC root"來掃描老年代;因此對于老年代來說,引用了老年代中對象的新生代對象,也會被老年代視為"GC roots", 當此階段耗時較長的時候,可以加入參數 -XX:+CMSScavengeBeforeRemark,在重新標記之前,先執行一次ygc,回收掉年輕代的無用對象,并將對象放入幸存代或者晉升到老年代,這樣再進行年輕代掃描時,只需要掃描幸存區的對象即可,一般幸存代非常小,這大大減少了掃描時間。
5、并發清理
通過以上5個階段的標記,老年代所有存活的對象已經被標記并且現在要通過Garbage Collector 采用清掃的方式回收那些不能用的對象。
這個階段只要是清除那些沒有標記的對象并且回收空間;
由于CMS并發清理階段用戶線程還在運行著,伴隨程序運行自然就還會有新的垃圾不斷產生,這一部分垃圾出現在標記過程之后,CMS無法在當次收集中處理掉它們,只好留下一次GC時再清理掉,這一部分就稱為"浮動垃圾"。
6、并發重置
這個階段并發執行,重新設置CMS算法內部的數據結構,準備下一個CMS生命周期的使用。
六、優化
1、一般CMS的GC耗時80%都在remark階段,如果發現remark階段停頓時間很長,可以嘗試添加該參數: -XX:+CMSScavengeBeforeRemark
2、CMS是基于標記-清除算法的,只會將標記為不存活的對象刪除,并不會移動對象整理內存空間,會造成內存碎片,這時我們需要用到這個參數:
-XX:+CMSFullGCsBeforeCompaction=n
●CMS GC要決定是否在full GC時做壓縮,會依賴幾個條件,其中:
①、UseCMSCompactAtFullCollection 與 CMSFullGCsBeforeCompaction 是搭配使用的,前者目前默認就是true了,也就是關鍵在后者。
②、用戶調用了System.gc(),而且DisableExplicitGC沒有開啟。
③、young gen報告接下來如果做增量收集會失敗,簡單來說也就是young gen預計 old gen 沒有足夠空間來容納下次young GC晉升的對象。
●上述三種條件的任意一種成立都會讓CMS決定這次做full GC時要做壓縮。
3、執行CMS GC的過程中,同時業務線程也在運行,當年輕代空間滿了,執行ygc時,需要將存活的對象放入到老年代,而此時老年代空間不足,這時CMS還沒有機會回收老年代產生的,或者在做Minor GC的時候,新生代空間放不下,需要放入老年代,而老年代也放不下而產生concurrent mode failure.
要確定發生concurrent mode failure 的原因是因為碎片造成的,還是Eden區有大對象直接晉升老年代造成的,一般有大量的對象晉升老年代容易導致這個錯,這種是存在優化空間的,要保證大部分對象盡可能的再新生代gc 掉。
4、CMS默認啟動的回收線程數目是(ParallelGCThreads + 3) / 4, 這里的ParallelGCThreads是年輕代的并行收集線程數;
年輕代的并行收集線程數默認是(ncpus <= 8) ? ncpus : 3 + ((ncpus * 5) / 8),可以通過
-XX:ParallelGCThreads = N 來調整;如果要直接設定CMS回收線程數,可以通過:
-XX:ParallelCMSThreads = n,注意這個n不能超過cpu線程數,需要注意的是增加gc線程數,就會和應用爭搶資源。