jvm學習 CMS垃圾收集器通俗理解

系統性學習請點擊jvm學習目錄
前面講了垃圾回收算法,那麼就開始介紹垃圾回收器吧,總共要寫CMS,G1,Shenandoah和ZGC。

總覽CMS

CMS垃圾收集器,其全稱是Concurrent Mark Sweep,併發標記清除。
從其名字Concurrent可以看出,首先它是併發的,這表明它與Serial收集器以及Parnew收集器或者Parallel Scavenge收集器這些單線程或者並行的收集器是不一樣的,這裏是併發的,併發代表,在進行垃圾收集的過程中,用戶線程也是可以併發進行的。
而從其名字Mark Sweep可以知道,它是使用標記清除算法的(如果不清楚可,這裏可以看執之前的垃圾回收算法)。使用了標記-清除算法,很明顯,它會很快,但也同時很明顯,它會產生內存碎片(內存不連續)。
總結一下,其最主要的特點是:

  • CMS垃圾收集器是面向老年代的,所以在實際jvm使用中,常常會會搭配其他新生代的垃圾收集器來配套使用。
  • CMS垃圾收集器的目的是追求時延最短,也就是停頓時間最短,它傾向於單次STW時間少,所以CMS垃圾收集器比較適合對響應速度要求較高的服務。
  • 併發收集,低停頓。

CMS收集器在垃圾回收過程中經歷四個階段:

  1. 初始標記
  2. 併發標記
  3. 重新標記
  4. 併發清除

在這裏插入圖片描述

下面對這四個階段分別進行介紹。

初始標記

該階段將GC Roots對象所直接引用的對象進行標記(這裏如果對GC Roots不熟悉可以點擊可達性分析)。所謂直接引用的對象也就是GC Roots對象節點的子節點。如下圖所示(畫的醜陋,見諒哈哈哈哈,然後這裏是三色標記法,黑色是子節點與本節點被掃描過,灰色是隻有本節點被掃描過,而白色是本節點未被掃描過):
在這裏插入圖片描述

黑色的就是初始標記中被標記的,而白色的就是沒有被標記的。
實際情況跟我畫的圖不一樣哈,實際情況中,這顆樹會很大的,GC Roots其直接引用的對象其實特別少,所以初始標記特別快,快到反應不過來。
但這裏需要注意的是,初始標記是不能被其他用戶線程干擾的,即使它很快,它也會STW(stop the world,該階段只允許GC線程運行,不允許用戶線程運行)。

併發標記

在併發標記階段,CMS會將剩下沒有掃描的對象全部都掃描,也就是將可達樹上完完整整的遍歷一遍,這個過程耗時較長,但是它不需要停頓。該階段是GC線程與用戶線程併發執行的。執行完之後的結果如下圖所示:
在這裏插入圖片描述
因爲是併發執行的,所以該階段當然就不會STW啦。
在該階段,如果用戶線程改變了對象間的引用關係,JVM會將新插入的引用記錄下來,等到重新標記這個階段來重新掃描被記錄的節點。

重新標記

既然是併發標記,那麼自然就是個併發可達性分析問題,當標記過程中用戶線程改變了對象引用的關係時,那麼就會出問題。
下面簡單講一下會出什麼問題,詳細瞭解請點擊JVM學習 併發可達性分析詳解,如果這一塊比較瞭解的話,下面的例子就不用看了,直接跳到黑字部分
問題在於:
在確定了GC Roots之後,我們繼續往下掃描,此時用戶線程可能會對可達性分析造成干擾,見下圖。
在這裏插入圖片描述
這裏JVM從0這個GC Roots開始掃描,當掃描了2時,正準備掃描其子節點3時,此時用戶線程對引用關係進行了修改,將2-3的引用關係刪除,添加了1-3的引用關係,如下圖所示(虛線是刪除)。

在這裏插入圖片描述
可以看到此時出問題了,即使是修改了之後,從我們用戶的眼光來看,3依然是可達的,但是在jvm角度來看,1是黑的,它和它的子節點不會再被掃描,所以掃描不到3,2是灰的,但是它與3之間沒有引用關係。此時,jvm就會錯誤的認爲3是不可達的。

於是,爲了擺脫這種錯誤出現的可能性,CMS採用了增量更新的辦法,即:當黑色對象插入新的指向白色對象的引用關係時,就將這個新插入的引用記錄下來,等我們的掃描(遍歷)結束之後,在以我們記錄中的黑色對象爲GC Roots,再掃描一遍。這裏需要注意,再次掃描的不是整個樹哦,而是那些有過添加引用操作的黑色對象。所以第二次掃描的時間會很短。

這裏在併發標記階段,CMS就正常的掃描,同時將新插入的引用關係記錄下來,而在重新標記階段,則會重新掃描被記錄被新插入引用關係的節點,從而將該節點下的新插入的子節點都掃描出來。從而保證了“不錯殺一個好人”。

經過上面的敘述,應該對重新標記這個階段有了深刻的理解了吧,總結一下,重新標記階段就是爲了修正併發標記期間,用戶線程繼續執行而導致標記產生變動的的那一部分對象的標記記錄。由於這個階段只是修正,所以執行時間比並發標記要短的多,不過也比初始標記要長一些。
同時,該階段是要STW的,也就是非併發的。你可以這樣想,重新標記就是爲了解決上一階段併發所搞得爛攤子,如果這裏還併發,那麼和上個階段有什麼區別?所以爲了修正,爲了不再弄出問題,該階段是一定要STW的

併發清除

該階段是最後一個階段,只是將經過前面三個標記階段最終被判斷爲死亡的對象給清除了就行。只是這個階段是併發的,也沒影響,因爲判斷被死亡的對象肯定是死的透透的,不可能復活的。
不過如果是這個階段,堆內存中出現了新的對象,無論這個對象是不是垃圾,CMS也無能爲力了,只能等到下一次major gc才能解決。這些新出現的對象被取名爲“浮動垃圾”。這也是CMS的一個侷限性。

總結

整體來看,整個CMS過程還是比較簡單的,我們記憶它可以採取這樣的記憶法:CMS=併發可達性分析∪標記清除算法,然後記住初始標記和重新標記是STW的基本就可以了。
雖說4個階段裏有兩個階段是要STW的,但實際兩個階段的時間是非常短的,所以也是符合了追求低時延的目標了。
然後需要切記的是,該收集器是面向老年代的,千萬不要弄混了,不要扯到新生代那裏去了。
下面來說下CMS的優點:

  • 併發
  • 低時延

而缺點也顯然:

  • 浮動垃圾無法處理,假如浮動垃圾過多,可能直接導致full gc,這裏需要提醒,不要覺得full gc就是簡單的minor gc+major
    gc,full gc會啓用Serial
    old收集器來處理老年代,它是單線程的非併發的,會STW很久,與CMS的低時延相比,這個簡直停頓到爆炸。
  • 因爲採用的標記-清除算法,所以毫無疑問會導致內存不連續,產生內存碎片。
  • CMS的回收線程數是(處理器核心數量+3)/4,當處理器數量少時,佔用的cpu資源會比較多,此時就不是很友好了,所以CMS比較適合多核心CPU的PC。

參考資料

  • 《深入理解jvm》周志明
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章