CMS垃圾回收和線上Full GC排查

背景

咱們上線Java服務的時候須要對其配置一些JVM參數,如堆空間大小、虛擬機棧大小、垃圾回收算法。對於年輕代和老年代咱們能夠配置不一樣的垃圾回收算法。在一些對rt要求很高的場景,服務不能有長時間的卡頓,CMS就能夠運用於此場景。算法

Concurrent Mark Sweep,是一款基於併發、使用標記清除算法的垃圾回收算法,只針對老年代進行垃圾回收。CMS收集器工做時,GC工做線程和用戶線程能夠併發執行,以達到下降STW時間的目的。segmentfault

開起VM選項-XX:+UseConcMarkSweepGC,表示對老年代的回收採用CMS。安全

前置知識

STW

首先,咱們須要釐清一個概念,即只有標記階段才須要STW。標記完成之後,須要清除的對象已經肯定,不管此時是否產生新的垃圾,都不影響對這些對象的清理。也就是說,清除階段是能夠設計成和用戶線程併發執行的。數據結構

JVM在暫停的時候,須要選準一個時機,因爲JVM系統運行期間的複雜性,不可能作到隨時暫停,所以引入了安全點(safepoint)的概念:程序只有在運行到安全點的時候,才能夠暫停下來。HotSpot採用主動中斷的方式,讓執行線程在運行期輪詢是否須要暫停的標誌,若須要則中斷掛起。HotSpot使用了幾條短小精煉的彙編指令即可完成安全點輪詢以及觸發線程中斷,所以對系統性能的影響幾乎能夠忽略不計。併發

可達性

可達性是指,若是一個對象會被至少一個程序中的可達對象經過直接或間接的方式引用,則稱該對象是可達的。更詳細地說,一個對象知足一下兩個條件之一,即被斷定爲可達的。性能

1.自己是根對象。根(root)是指由堆之外空間訪問的對象。JVM會將如下對象標記爲根:a.虛擬機棧(棧幀中的本地變量表)中引用的對象;b.方法區中的類靜態屬性引用的對象;c.方法區中的常量引用的對象;d.本地方法棧中JNI的引用對象。spa

2.被一個可達的對象引用。線程

CMS的幾個階段

CMS將可達性分析分解成兩個階段:a.僅掃描與根節點直接關聯的對象; b.繼續向下掃描完全部對象。所以,標記階段也被拆分紅兩個階段,即初始標記併發標記設計

CMS完整的收集過程以下:日誌

  1. 初始標記(init-mark):僅掃描與根節點直接關聯的對象並標記,這個階段必須STW, 因爲跟節點數量有限,因此這個過程很是短暫。
  2. 併發標記(concurrent-marking):與用戶線程併發標記。這個階段在初始標記的基礎上繼續向下追溯標記。在併發標記階段,用戶線程和標記線程併發執行,因此用戶不會感覺到停頓。
  3. 併發預清理(concurrent-precleaning):與用戶線程併發進行。在併發標記階段一些對象的引用已經發生了變化,precleaning會發現這些引用關係的改變,並將存活的對象標記。舉個例子:若是線程A有一個指向對象X的引用,並將該引用傳遞給了線程B,CMS須要記錄下線程B持有了對象X,即便線程A已經不存在了。precleaning是爲了減小下一階段「從新標記」的工做量,由於remark階段會STW
  4. 從新標記(remark) remark階段會STW。若是應用正在併發運行且在不斷地改變對象引用,CMS則不能準確地肯定某個對象是否存活。因此CMS會在remark階段STW,從而獲取全部引用關係的改變。
  5. 併發清理(concurrent-sweeping):清理垃圾對象,這個階段GC線程和用戶線程併發執行。
  6. 併發重置(concurrent-reset):重置CMS收集器的數據結構,作好下一次執行GC任務的準備工做。

alt text

線上Full GC分析

線上某服務的老年代配置了CMS,但卻在gc.log發現連續Full GC的問題。JVM參數配置以下:

-XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSInitiatingOccupancyFraction=68

參數的意義是:在老年代到68%的時候,會觸發一次CMS GC,應該是出現相似以下的日誌:

T20:10:37.803+0800: 3246087.559: [CMS-concurrent-mark-start]
T20:10:38.463+0800: 3246088.220: [CMS-concurrent-mark: 0.661/0.661 secs] [Times: user=3.17 sys=0.56, real=0.66 secs]
T20:10:38.463+0800: 3246088.220: [CMS-concurrent-preclean-start]
T20:10:38.552+0800: 3246088.309: [CMS-concurrent-preclean: 0.069/0.089 secs] [Times: user=0.14 sys=0.04, real=0.09 secs]_</span>
T20:10:38.552+0800: 3246088.309: [CMS-concurrent-abortable-preclean-start]

但線上環境的日誌卻出現以下的狀況:

alt text

老年代配置了900M,但卻在只使用了50+M的時候觸發了Full GC,並且是在短暫的時間內連續觸發。

配置了CMS卻觸發Full GC,有如下幾種可能:

  1. 大對象分配時,年輕代不夠,直接晉升到老年代,老年代空間也不夠,觸發 Full GC(老年代還剩800+M,顯然不可能)
  2. 內存碎片致使(因爲CMS是基於標記清除算法的,全部會致使內存碎片,但經過grep -i "cms" gc.log,JVM還沒有觸發過CMS回收,因此也不存在內存碎片的說法)
  3. CMS GC失敗致使(從gc.log並未找到concurrent mode failure的記錄,排除)
  4. jmap -histo(人爲執行該命令)

經筆者回憶,在中午快12點的時候確實登陸過線上機,執行過jmap -histo:live命令,經驗證,手動執行jmap -histo:live,也確實會在gc.log出現觸發 Full GC的現象,問題獲得驗證。

原文連接

https://segmentfault.com/a/11...

相關文章
相關標籤/搜索