GC Cause解析

通常,在基於Java生態體系中的應用程式丟擲異常時,生產環境都會透過gc log

[當然,也有2愣子直接去線上環境進行各種騷操作]

去捕獲各種可疑線索,以便快速、高效定位及解決問題。

本文主要基於 Hotspot VM 中“CMS”垃圾回收策略的一些實際場景進行彙總,

[涉及的基礎概念暫不在本章贅述]

簡要透過部分原始碼對引起GC現象的根本原因進行分析以及對排查方法進行總結。另外,本文專業術語較多,有一定的閱讀門檻,如對JVM體系所涉及的記憶體分配及垃圾回收沒有理論支撐以及實戰經驗,還請去官網查閱相關材料。

在我們測試環境或者預釋出環境,通常透過如下命令檢視某一特定Java應用程式的GC詳細情況:

[administrator@JavaLangOutOfMemory luga % ]jstat -gccause pid xxxxx

以確認上次GC的原因和當前GC的原因。

GC Cause,顧名思義,就是引起發生垃圾回收的因素。只有瞭解是什麼原因引起的 GC,以及每次的時間花費情況,才能有效去定位、分析問題所在。但是要具體分析 GC 的問題,首先要讀懂 GC Cause,即 JVM在何種場景下選擇進行 GC 操作,具體 GC Cause 的分類可參考Hotspot 原始碼:

src/share/vm/gc/shared/gcCause。hpp

src/share/vm/gc/shared/gcCause。cpp

const char* GCCause::to_string(GCCause::Cause cause) { switch (cause) { case _java_lang_system_gc: return “System。gc()”; case _full_gc_alot: return “FullGCAlot”; case _scavenge_alot: return “ScavengeAlot”; case _allocation_profiler: return “Allocation Profiler”; case _jvmti_force_gc: return “JvmtiEnv ForceGarbageCollection”; case _gc_locker: return “GCLocker Initiated GC”; case _heap_inspection: return “Heap Inspection Initiated GC”; case _heap_dump: return “Heap Dump Initiated GC”; case _wb_young_gc: return “WhiteBox Initiated Young GC”; case _wb_conc_mark: return “WhiteBox Initiated Concurrent Mark”; case _wb_full_gc: return “WhiteBox Initiated Full GC”; case _no_gc: return “No GC”; case _allocation_failure: return “Allocation Failure”; case _tenured_generation_full: return “Tenured Generation Full”; case _metadata_GC_threshold: return “Metadata GC Threshold”; case _metadata_GC_clear_soft_refs: return “Metadata GC Clear Soft References”; case _cms_generation_full: return “CMS Generation Full”; case _cms_initial_mark: return “CMS Initial Mark”; case _cms_final_remark: return “CMS Final Remark”; case _cms_concurrent_mark: return “CMS Concurrent Mark”; case _old_generation_expanded_on_last_scavenge: return “Old Generation Expanded On Last Scavenge”; case _old_generation_too_full_to_scavenge: return “Old Generation Too Full To Scavenge”; case _adaptive_size_policy: return “Ergonomics”; case _g1_inc_collection_pause: return “G1 Evacuation Pause”; case _g1_humongous_allocation: return “G1 Humongous Allocation”; case _dcmd_gc_run: return “Diagnostic Command”; case _last_gc_cause: return “ILLEGAL VALUE - last gc cause - ILLEGAL VALUE”; default: return “unknown GCCause”; } ShouldNotReachHere();}

結合原始碼,我們可以看到,在實際的專案中,針對GC此處產生問題的分析重點需要關注的以下幾個GC Cause:

1、System。gc():即,顯性手動觸發GC操作

2、CMS:CMS GC 在執行過程中的一些動作,重點需要關注 CMS Initial Mark 和 CMS Final Remark 兩個 STW 階段

3、Promotion Failure:Old 區沒有足夠的空間分配給 Young 區晉升的物件(即使總可用記憶體足夠大)

4、Concurrent Mode Failure:CMS GC 執行期間,Old 區所預留的空間不足以分配給新建立的物件,此時收集器會發生退化,甚至嚴重影響 GC 效能

5、GCLocker Initiated GC:如果執行緒執行在 JNI 臨界區操作時,剛好需要進行 GC操作,此時 GC Locker 將會阻止 GC 操作的發生,同時阻止其他執行緒進入 JNI 臨界區,直到最後一個執行緒退出臨界區時觸發一次 GC

在一次實際的業務場景處理的過程中,如何判斷是 GC 操作導致的故障,還是應用系統本身引發 GC 問題?這裡主要結合相關資料資訊(例如:監控資料、GC Log日誌檔案、資源使用情況以及可獲得的HeapDump/ThreadDump及CoreDump等相關轉儲檔案)進行合理分析。圍繞“GC 耗時增大、執行緒 Block 增多、慢查詢增多、CPU 負載高“等核心要素,準確定位、判斷到底哪個是罪魁禍首。

畢竟,不同的根因,後續的分析方法不盡相同。如果是 CPU 負載高,那可能需要用火焰圖或者藉助Nmon工具結合應用程式看下相關熱點;如果是慢查詢增多那可能需要觀察下 DB 資源情況;如果是執行緒 Block 引起那可能需要判斷是否存在鎖競爭的情況;反之,如果各個核心要素證明都沒有問題,那麼罪魁禍首可能存在於GC這塊,So,我們就需要以GC為切入點繼續分析 GC 問題,直到將其Fix掉為止。

綜上所述,只有透過對GC Cause的相關原始碼以及產生的相關因素進行剖析,在應用程式出現記憶體問題時才能遊刃有餘去處理。

- EOF -

如果您喜歡本文,歡迎

點贊

收藏

留言

,或者點選右下角,把文章分享給你的朋友們~~~

GC Cause解析

GC Cause解析

Luga Lee

“路,在自己腳下~”