本文已收錄至Github,推薦閱讀 ?? Java隨想錄
微信公眾號:Java隨想錄
CSDN: 碼農BookSea
目錄應該相信,自己是生活的戰勝者,——雨果
- CMS簡介
- 運作程序
- CMS的缺陷
- 處理器資源敏感
- 無法處理“浮動垃圾”
- 記憶體碎片
縱觀全書《深入理解JVM虛擬機》第三版,在垃圾回收器這一篇章,對于CMS的筆墨是非常多的,CMS收集器是HotSpot虛擬機追求低停頓的第一次成功嘗試,CMS 可以說是垃圾回收器的一個里程碑,其開啟了 GC 回收器關注 GC 停頓時間的歷史,
CMS簡介
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器,啟用引數:-XX:+UseConMarkSweepGC,使用的是標記-清除演算法,在這之前的垃圾回收器,要么就是串行垃圾回收方式,要么就是關注系統吞吐量,而 CMS 垃圾回收器的出現,則打破了這個尷尬的局面,JDK9之后使用CMS垃圾收集器后,默認年輕代就為ParNew收集器,并且不可更改,同時JDK9之后被標記為不推薦使用,JDK14就被洗掉了,
CMS 垃圾回收器之所以能夠實作對 GC 停頓時間的控制,其關鍵是三色標記演算法(不了解的同學去翻我之前寫的文章),通過三色標記演算法,實作了垃圾回收執行緒與用戶執行緒并發執行,從而極大地降低了系統回應時間,
運作程序
CMS整個程序分為四個步驟,包括:
- 初始標記(CMS initial mark)
- 并發標記(CMS concurrent mark)
- 重新標記(CMS remark)
- 并發清除(CMS concurrent sweep)
其中初始標記、重新標記這兩個步驟仍然需要“Stop The World”,
- 初始標記僅僅只是標記一下GC Roots能直接關聯到的物件,速度很快;
- 并發標記階段就是從GC Roots的直接關聯物件開始遍歷整個物件圖的程序,這個程序是四個階段中耗時最長的,但是不需要停頓用戶執行緒,可以與垃圾收集執行緒一起并發運行;
- 而重新標記階段則是為了修正并發標記期間,因用戶程式繼續運作而導致標記產生變動的那一部分物件的標記記錄,這里使用的是增量更新,這個階段的停頓時間通常會比初始標記階段稍長一些,但也遠比并發標記階段的時間短;
- 最后是并發清除階段,清理洗掉掉標記階段判斷的已經死亡的物件,由于不需要移動存活物件,所以這個階段也是可以與用戶執行緒同時并發的,
由于在整個程序中耗時最長的并發標記和并發清除階段中,垃圾收集器執行緒都可以與用戶執行緒一起作業,所以從總體上來說,CMS收集器的記憶體回收程序是與用戶執行緒一起并發執行的,
CMS的缺陷
CMS有三個最大的缺點
處理器資源敏感
CMS收集器是比較消耗CPU資源的,對處理器資源是比較敏感的,在并發階段,它不會導致用戶執行緒停頓,但會占用了一部分執行緒(或者說處理器的計算能力)而導致應用程式變慢,降低總吞吐量,
低延遲和高吞吐,往往無法同時達成,低延遲有時是犧牲高吞吐換得的,有得必有失,
CMS默認啟動的回收執行緒數是(處理器核心數量+3)/4,也就是說,如果處理器核心數在四個或以上,并發回收時垃圾收集執行緒只占用不超過25%的處理器運算資源,但是當處理器核心數量不足四個時,CMS對用戶程式的影響就可能變得很大,如果應用本來的處理器負載就很高,還要分出一半的運算能力去執行收集器執行緒,就可能導致用戶程式的執行速度忽然大幅降低,
為了緩解這種情況,虛擬機提供了一種稱為“增量式并發收集器”(Incremental Concurrent Mark Sweep/i-CMS)的CMS收集器變種,在并發標記、清理的時候讓收集器執行緒、用戶執行緒交替運行,盡量減少垃圾收集執行緒的獨占資源的時間,這樣整個垃圾收集的程序會更長,但對用戶程式的影響就會顯得較少一些,直觀感受是速度變慢的時間更多了,但速度下降幅度就沒有那么明顯,實踐證明增量式的CMS收集器效果很一般,從JDK 7開始,i-CMS模式已經被宣告為“deprecated”,即已過時不再提倡用戶使用,到 JDK 9發布后i-CMS模式被完全廢棄,
無法處理“浮動垃圾”
由于CMS收集器無法處理“浮動垃圾”(Floating Garbage),有可能出現“Con-current Mode Failure”失敗進而導致另一次完全“Stop The World”的Full GC的產生,
在CMS的并發標記和并發清理階段,用戶執行緒是還在繼續運行的,程式在運行自然就還會伴隨有新的垃圾物件不斷產生,但這一部分垃圾物件是出現在標記程序結束以后,CMS無法在當次收集中處理掉它們,只好留待下一次垃圾收集時再清理掉,這一部分垃圾就稱為“浮動垃圾”,
由于在垃圾收集階段用戶執行緒還需要持續運行,那就還需要預留足夠記憶體空間提供給用戶執行緒使用,因此CMS收集器不能像其他收集器那樣等待到老年代幾乎完全被填滿了再進行收集,在JDK5的默認設定下,CMS收集器當老年代使用了68%的空間后就會被激活,到了JDK 6時,CMS收集器的啟動閾值就已經默認提升至92%,我們可以通過 -XX:CMSInitiatingOccupancyFraction 引數自行調節,
但這又會更容易面臨另一種風險:要是CMS運行期間預留的記憶體無法滿足程式分配新物件的需要,就會出現一次“并發失敗”(Concurrent Mode Failure),這時候虛擬機將不得不啟動后備預案:凍結用戶執行緒的執行,臨時啟用Serial Old收集器來重新進行老年代的垃圾收集,但這樣停頓時間就很長了,
記憶體碎片
CMS是一款基于“標記-清除”演算法實作的收集器,在垃圾收集演算法的時候我們說過,標記-清除會產生記憶體碎片,空間碎片過多時,將會給大物件分配帶來很大麻煩,往往會出現老年代還有很多剩余空間,但就是無法找到足夠大的連續空間來分配當前物件,而不得不提前觸發一次Full GC的情況,
為了解決這個問題,CMS收集器提供了一個-XX:+UseCMS-CompactAtFullCollection開關引數(默認是開啟的,此引數從JDK 9開始廢棄),用于在CMS收集器不得不進行Full GC時開啟記憶體碎片的合并整理程序,由于這個記憶體整理必須移動存活物件,這樣空間碎片問題是解決了,但停頓時間又會變長,因此虛擬機設計者們還提供了另外一個引數-XX:CMSFullGCsBefore-Compaction(此引數從JDK 9開始廢棄),這個引數的作用是要求CMS收集器在執行過若干次(數量由引數值決定)不整理空間的Full GC之后,下一次進入Full GC前會先進行碎片整理(默認值為0,表示每次進入Full GC時都進行碎片整理),
如果本篇博客有任何錯誤和建議,歡迎給我留言指正,文章持續更新,可以關注公眾號第一時間閱讀,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/542663.html
標籤:Java
下一篇:CentOS中docker的使用
