【即插即用的硬幹貨】線上系統出現頻繁 JVM FullGC 時,應該如何排查和處理?
還沒關注?
快動動手指!
聊技術、論職場!
為IT人打造一個“有溫度”的 狸貓技術窩
背景
我們上線Java服務的時候需要對其配置一些JVM引數,如堆空間大小、虛擬機器棧大小、垃圾回收演算法。對於年輕代和老年代我們可以配置不同的垃圾回收演算法。在一些對 rt
要求很高的場景,服務不能有長時間的卡頓, CMS
就可以運用於此場景。
Concurrent Mark Sweep
,是一款基於併發、使用標記清除演算法的垃圾回收演算法,只針對老年代進行垃圾回收。
CMS收集器工作時,GC工作執行緒和使用者執行緒可以併發執行,以達到降低 STW
時間的目的。
開起VM選項 -XX:+UseConcMarkSweepGC
,表示對老年代的回收採用CMS。
前置知識
STW
首先,我們需要理清一個概念,即只有 標記
階段才需要 STW
。
標記完成以後,需要清除的物件已經確定,無論此時是否產生新的垃圾,都不影響對這些物件的清理。也就是說, 清除
階段是可以設計成和使用者執行緒併發執行的。
JVM在暫停的時候,需要選準一個時機,由於JVM系統執行期間的複雜性,不可能做到隨時暫停,因此引入了 安全點(safepoint)
的概念:程式只有在執行到安全點的時候,才可以暫停下來。
HotSpot
採用主動中斷的方式,讓執行執行緒在執行期輪詢是否需要暫停的標誌,若需要則中斷掛起。
HotSpot
使用了幾條短小精煉的彙編指令便可完成安全點輪詢以及觸發執行緒中斷,因此對系統性能的影響幾乎可以忽略不計。
可達性
可達性
是指,如果一個物件會被至少一個程式中的可達物件通過直接或間接的方式引用,則稱該物件是 可達的
。
更詳細地說,一個物件滿足以下兩個條件之一,即被判定為可達的。
-
本身是根物件 。根(root)是指由堆以外空間訪問的物件。JVM會將以下物件標記為根:
-
虛擬機器棧(棧幀中的本地變量表)中引用的物件;
-
方法區中的類靜態屬性引用的物件;
-
方法區中的常量引用的物件;
-
本地方法棧中JNI的引用物件
-
被一個可達的物件引用 。
CMS的幾個階段
CMS
將可達性分析分解成兩個階段:
-
僅掃描與根節點直接關聯的物件;
-
繼續向下掃描完所有物件。
因此, 標記
階段也被拆分成兩個階段,即 初始標記
和 併發標記
。
CMS完整的收集過程如下:
-
初始標記(init-mark)
:僅掃描與根節點直接關聯的物件並標記,這個階段必須STW
, 由於跟節點數量有限,所以這個過程非常短暫。 -
併發標記(concurrent-marking)
:與使用者執行緒併發標記。這個階段在初始標記的基礎上繼續向下追溯標記。在併發標記階段,使用者執行緒和標記執行緒併發執行,所以使用者不會感受到停頓。 -
併發預清理(concurrent-precleaning)
:與使用者執行緒併發進行。在併發標記階段一些物件的引用已經發生了變化,precleaning
會發現這些引用關係的改變,並將存活的物件標記。舉個例子:如果執行緒A有一個指向物件X的引用,並將該引用傳遞給了執行緒B,CMS需要記錄下執行緒B持有了物件X,即使執行緒A已經不存在了。precleaning
是為了減少下一階段“重新標記”的工作量,因為remark
階段會STW
。 -
重新標記(remark)
:remark
階段會STW
。如果應用正在併發執行且在不斷地改變物件引用,CMS
則不能準確地確定某個物件是否存活。所以CMS
會在remark
階段STW
,從而獲取所有引用關係的改變。 -
併發清理(concurrent-sweeping)
:清理垃圾物件,這個階段GC執行緒和使用者執行緒併發執行。 -
併發重置(concurrent-reset)
:重置CMS收集器的資料結構,做好下一次執行GC任務的準備工作。
線上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]_ T20:10:38.552+0800: 3246088.309: [CMS-concurrent-abortable-preclean-start]
但線上環境的日誌卻出現如下的情況:
老年代配置了900M,但卻在只使用了50+M的時候觸發了Full GC,而且是在短暫的時間內連續觸發。
配置了CMS卻觸發Full GC,有以下幾種可能:
-
大物件分配時,年輕代不夠,直接晉升到老年代,老年代空間也不夠,觸發 Full GC(老年代還剩800+M,顯然不可能)
-
記憶體碎片導致(由於CMS是基於標記清除演算法的,所有會導致記憶體碎片,但通過grep -i "cms" gc.log,JVM尚未觸發過CMS回收,所以也不存在記憶體碎片的說法)
-
CMS GC失敗導致(從gc.log並未找到concurrent mode failure的記錄,排除)
-
jmap -histo(人為執行該命令)
經筆者回憶,在中午快12點的時候確實登入過線上機,執行過 jmap -histo:live
命令,經驗證,手動執行 jmap -histo:live
,也確實會在gc.log出現觸發 Full GC的現象,問題得到驗證。
END
本文作者: 撲火的蛾
本文來源:
https://segmentfault.com/a/1190000015182001
長按下圖二維碼,即刻關注【 狸貓技術窩 】
阿里、京東、美團、位元組跳動
頂尖技術專家 坐鎮
為IT人打造一個 “有溫度” 的技術窩!