天天看點

【最佳實踐】一文掌握并應用Elasticsearch中的GC實作垃圾日志處理

作者介紹

魏彬,普翔科技 CTO,開源軟體愛好者,中國第一位 Elastic 認證工程師,《Elastic日報》和 《ElasticTalk》社群項目發起人,被 elastic 中國公司授予 2019 年度合作夥伴架構師特别貢獻獎。對 Elasticsearch、Kibana、Beats、Logstash、Grafana 等開源軟體有豐富的實踐經驗,為零售、金融、保險、證券、科技等衆多行業的客戶提供過咨詢和教育訓練服務,幫助客戶在實際業務中找準開源軟體的定位,實作從 0 到 1 的落地、從 1 到 N 的拓展,産生實際的業務價值。

如果你關注過 Elasticsearch 的日志,可能會看到如下類似的内容:

[2018-06-30T17:57:23,848][WARN ][o.e.m.j.JvmGcMonitorService] [qoo--eS] [gc][228384] overhead, spent [2.2s] collecting in the last [2.3s]
[2018-06-30T17:57:29,020][INFO ][o.e.m.j.JvmGcMonitorService] [qoo--eS] [gc][old][228385][160772] duration [5s], collections [1]/[5.1s], total [5s]/[4.4d], memory [945.4mb]->[958.5mb]/[1007.3mb], all_pools {[young] [87.8mb]->[100.9mb]/[133.1mb]}{[survivor] [0b]->[0b]/[16.6mb]}{[old] [857.6mb]->[857.6mb]/[857.6mb]}           

看到其中的[gc]關鍵詞你也猜到了這是與 GC 相關的日志,那麼你了解每一部分的含義嗎?如果不了解,你可以繼續往下看了。

我們先從最簡單的看起:

1、第一部分是日志發生的時間

2、第二部分是日志級别,這裡分别是WARN和INFO

3、第三部分是輸出日志的類,我們後面也會講到這個類

4、第四部分是目前 ES 節點名稱

5、第五部分是 GC 關鍵詞,我們就從這個關鍵詞聊起。

友情提示:對 GC 已經了如指掌的同學,可以直接翻到最後看答案。

1、什麼是 GC

GC,全稱是 Garbage Collection (垃圾收集)或者 Garbage Collector(垃圾收集器)。

在使用 C語言程式設計的時候,我們要手動的通過 malloc 和 free來申請和釋放資料需要的記憶體,如果忘記釋放記憶體,就會發生記憶體洩露的情況,即無用的資料占用了寶貴的記憶體資源。而Java 語言程式設計不需要顯示的申請和釋放記憶體,因為 JVM 可以自動管理記憶體,這其中最重要的一部分就是 GC,即 JVM 可以自主地去釋放無用資料(垃圾)占用的記憶體。

我們研究 GC 的主要原因是 GC 的過程會有 Stop The World(STW)的情況發生,即此時使用者線程會停止工作,如果 STW 的時間過長,則應用的可用性、實時性等就下降的很厲害。

GC主要解決如下3個問題:

1、如何找到垃圾?

2、如何回收垃圾?

3、何時回收垃圾?

我們一個個來看下。

1.1、如何找到垃圾

所謂垃圾,指的是不再被使用(引用)的對象。Java 的對象都是在堆(Heap)上建立的,我們這裡預設也隻讨論堆。那麼現在問題就變為如何判定一個對象是否還有被引用,思路主要有如下兩種:

1、引用計數法,即在對象被引用時加1,去除引用時減1,如果引用值為0,即表明該對象可回收了。

2、可達性分析法,即通過周遊已知的存活對象(GC Roots)的引用鍊來标記出所有存活對象

方法1:簡單粗暴效率高,但準确度不行,尤其是面對互相引用的垃圾對象時無能為力。

方法2:是目前常用的方法,這裡有一個關鍵是 GC Roots,它是判定的源頭,感興趣的同學可以自己去研究下,這裡就不展開講了。

【最佳實踐】一文掌握并應用Elasticsearch中的GC實作垃圾日志處理

1.2、如何回收垃圾?

垃圾找到了,該怎麼回收呢?看起來似乎是個很傻的問題。直接收起來扔掉不就好了?!對應到程式的操作,就是直接将這些對象占用的空間标記為空閑不就好了嗎?那我們就來看一下這個基礎的回收算法:标記-清除(Mark-Sweep)算法。

1.2.1 标記-清除 算法(Mark Sweep)

該算法很簡單,使用通過可達性分析分析方法标記出垃圾,然後直接回收掉垃圾區域。它的一個顯著問題是一段時間後,記憶體會出現大量碎片,導緻雖然碎片總和很大,但無法滿足一個大對象的記憶體申請,進而導緻 OOM,而過多的記憶體碎片(需要類似連結清單的資料結構維護),也會導緻标記和清除的操作成本高,效率低下,如下圖所示:

【最佳實踐】一文掌握并應用Elasticsearch中的GC實作垃圾日志處理
1.2.2 複制算法(Copying)

為了解決上面算法的效率問題,有人提出了複制算法。它将可用記憶體一分為二,每次隻用一塊,當這一塊記憶體不夠用時,便觸發 GC,将目前存活對象複制(Copy)到另一塊上,以此往複。這種算法高效的原因在于配置設定記憶體時隻需要将指針後移,不需要維護連結清單等。但它最大的問題是對記憶體的浪費,使用率隻有 50%。

但這種算法在一種情況下會很高效:Java 對象的存活時間極短。據 IBM 研究,Java 對象高達 98% 是朝生夕死的,這也意味着每次 GC 可以回收大部分的記憶體,需要複制的資料量也很小,這樣它的執行效率就會很高。

【最佳實踐】一文掌握并應用Elasticsearch中的GC實作垃圾日志處理
1.2.3 标記-整理算法(Mark Compact)

該算法解決了第1中算法的記憶體碎片問題,它會在回收階段将所有記憶體做整理,如下圖所示:

【最佳實踐】一文掌握并應用Elasticsearch中的GC實作垃圾日志處理

但它的問題也在于增加了整理階段,也就增加了 GC 的時間。

1.2.4 分代收集算法(Generation Collection)

既然大部分 Java 對象是朝生夕死的,那麼我們将記憶體按照 Java 生存時間分為 新生代(Young) 和 老年代(Old),前者存放短命僧,後者存放長壽佛,當然長壽佛也是由短命僧更新上來的。然後針對兩者可以采用不同的回收算法,比如對于新生代采用複制算法會比較高效,而對老年代可以采用标記-清除或者标記-整理算法。這種算法也是最常用的。JVM Heap 分代後的劃分一般如下所示,新生代一般會分為 Eden、Survivor0、Survivor1區,便于使用複制算法。

【最佳實踐】一文掌握并應用Elasticsearch中的GC實作垃圾日志處理

将記憶體分代後的 GC 過程一般類似下圖所示:

【最佳實踐】一文掌握并應用Elasticsearch中的GC實作垃圾日志處理

1、對象一般都是先在 Eden 區建立

2、當 Eden 區滿,觸發 Young GC,此時将 Eden中還存活的對象複制到 S0中,并清空 Eden區後繼續為新的對象配置設定記憶體

3、當 Eden 區再次滿後,觸發又一次的 Young GC,此時會将 Eden和S0中存活的對象複制到 S1中,然後清空Eden和S0後繼續為新的對象配置設定記憶體

4、每經過一次 Young GC,存活下來的對象都會将自己存活次數加1,當達到一定次數後,會随着一次 Young GC 晉升到 Old區

5、Old區也會在合适的時機進行自己的 GC

1.2.5 常見的垃圾收集器

前面我們講了衆多的垃圾收集算法,那麼其具體的實作就是垃圾收集器,也是我們實際使用中會具體用到的。現代的垃圾收集機制基本都是分代收集算法,而 Young與 Old區分别有不同的垃圾收集器,簡單總結如下圖:

【最佳實踐】一文掌握并應用Elasticsearch中的GC實作垃圾日志處理

從上圖我們可以看到 Young與 Old區有不同的垃圾收集器,實際使用時會搭配使用,也就是上圖中兩兩連線的收集器是可以搭配使用的。這些垃圾收集器按照運作原理大概可以分為如下幾類:

• Serial GC,串行,單線程的收集器,運作 GC 時需要停止所有的使用者線程,且隻有一個 GC 線程

• Parallel GC,并行,多線程的收集器,是 Serial 的多線程版,運作時也需要停止所有使用者線程,但同時運作多個 GC 線程,是以效率高一些

• Concurrent GC,并發,多線程收集器,GC 分多階段執行,部分階段允許使用者線程與 GC 線程同時運作,這也就是并發的意思,大家要和并行做一個區分。

• 其他

我們下面簡單看一下他們的運作機制。

1.2.5.1 Serial GC

該類 Young區的為 Serial GC,Old區的為Serial Old GC。執行大緻如下所示:

【最佳實踐】一文掌握并應用Elasticsearch中的GC實作垃圾日志處理

1.2.5.2 Parallel GC

該類Young 區的有 ParNew和 Parallel Scavenge,Old 區的有Parallel Old。其運作機制如下,相比 Serial GC ,其最大特點在于 GC 線程是并行的,效率高很多:

【最佳實踐】一文掌握并應用Elasticsearch中的GC實作垃圾日志處理

1.2.5.3 Concurrent Mark-Sweep GC

該類目前隻是針對 Old 區,最常見就是CMS GC,它的執行分為多個階段,隻有部分階段需要停止使用者程序,這裡不詳細介紹了,感興趣可以去找相關文章來看,大體執行如下:

【最佳實踐】一文掌握并應用Elasticsearch中的GC實作垃圾日志處理
1.2.5.4 其他

目前最新的 GC 有G1GC和ZGC,其運作機制與上述均不相同,雖然他們也是分代收集算法,但會把 Heap 分成多個 region 來做處理,這裡不展開講,感興趣的可以參看最後參考資料的内容。

【最佳實踐】一文掌握并應用Elasticsearch中的GC實作垃圾日志處理
1.2.6 Elasticsearch 的 GC 組合

Elasticsearch 預設的 GC 配置是CMS GC ,其 Young 區用 ParNew,Old 區用CMS,大家可以在 config/jvm.options中看到如下的配置:

## GC configuration
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=75
-XX:+UseCMSInitiatingOccupancyOnly           

1.3 何時進行回收?

現在我們已經知道如何找到和回收垃圾了,那麼什麼時候回收呢?簡單總結如下:

1、Young 區的GC 都是在 Eden 區滿時觸發

2、Serial Old 和 Parallel Old 在 Old 區是在 Young GC 時預測Old 區是否可以為 young 區 promote 到 old 區 的 object 配置設定空間,如果不可用則觸發 Old GC。這個也可以了解為是 Old區滿時。

3、CMS GC 是在 Old 區大小超過一定比例後觸發,而不是 Old 區滿。這個原因在于 CMS GC 是并發的算法,也就是說在 GC 線程收集垃圾的時候,使用者線程也在運作,是以需要預留一些 Heap 空間給使用者線程使用,防止由于無法配置設定空間而導緻 Full GC 發生。

2、GC Log 如何閱讀

前面講了這麼多,終于可以回到開篇的問題了,我們直接來看答案

[2018-06-30T17:57:23,848][WARN ][o.e.m.j.JvmGcMonitorService] [qoo--eS] [gc][228384] overhead, spent [2.2s] collecting in the last [2.3s]           

gc 在最近 2.3 s 内花了 2.2s 用來做垃圾收集,這占比似乎有些過了,請抓緊來關注下。

[2018-06-30T17:57:29,020][INFO ][o.e.m.j.JvmGcMonitorService] [qoo--eS] [gc][old][228385][160772] duration [5s], collections [1]/[5.1s], total [5s]/[4.4d], memory [945.4mb]->[958.5mb]/[1007.3mb], all_pools {[young] [87.8mb]->[100.9mb]/[133.1mb]}{[survivor] [0b]->[0b]/[16.6mb]}{[old] [857.6mb]->[857.6mb]/[857.6mb]}           

我們直接來看具體的含義好了,相信有了前面的 GC 基礎知識,大家在看這裡解釋的時候就非常清楚了。

• gc這是第228385次 GC 檢查

• duration [本次檢查到的 GC 總耗時 5 秒,可能是多次的加和],

• collections [從上次檢查至今總共發生1次 GC]/[從上次檢查至今已過去 5.1 秒],

• total [本次檢查到的 GC 總耗時為 5 秒]/[從 JVM 啟動至今發生的 GC 總耗時為 4.4 天],

• memory [ GC 前 Heap memory 空間]->[GC 後 Heap memory 空間]/[Heap memory 總空間],

• all_pools(分代部分的詳情) {young 區->[GC後 Memory]/[young區 Memory 總大小] } {survivor 區->[GC後 Memory]/[survivor區 Memory 總大小] }{old 區->[GC後 Memory]/[old區 Memory 總大小] }

3. 看看源碼

從日志中我們可以看到輸出這些日志的類名叫做JvmGcMonitorService,我們去源碼中搜尋很快會找到

它/Users/rockybean/code/elasticsearch/core/src/main/java/org/elasticsearch/monitor/jvm/JvmGcMonitorService.java,這裡就不詳細展開講解源碼了,它執行的内容大概如下圖所示:

【最佳實踐】一文掌握并應用Elasticsearch中的GC實作垃圾日志處理

關于列印日志的格式在源碼也有,如下所示:

private static final String SLOW_GC_LOG_MESSAGE =
"[gc][{}][{}][{}] duration [{}], collections [{}]/[{}], total [{}]/[{}], memory [{}]->[{}]/[{}], all_pools {}";
private static final String OVERHEAD_LOG_MESSAGE = "[gc][{}] overhead, spent [{}] collecting in the last [{}]";           

另外細心的同學會發現輸出的日志中 gc 隻分了 young 和 old ,原因在于 ES 對 GC Name 做了封裝,封裝的類為:org.elasticsearch.monitor.jvm.GCNames,相關代碼如下:

public static String getByMemoryPoolName(String poolName, String defaultName) {
        if ("Eden Space".equals(poolName) || "PS Eden Space".equals(poolName) || "Par Eden Space".equals(poolName) || "G1 Eden Space".equals(poolName)) {
            return YOUNG;
        }
        if ("Survivor Space".equals(poolName) || "PS Survivor Space".equals(poolName) || "Par Survivor Space".equals(poolName) || "G1 Survivor Space".equals(poolName)) {
            return SURVIVOR;
        }
        if ("Tenured Gen".equals(poolName) || "PS Old Gen".equals(poolName) || "CMS Old Gen".equals(poolName) || "G1 Old Gen".equals(poolName)) {
            return OLD;
        }
        return defaultName;
    }
    public static String getByGcName(String gcName, String defaultName) {
        if ("Copy".equals(gcName) || "PS Scavenge".equals(gcName) || "ParNew".equals(gcName) || "G1 Young Generation".equals(gcName)) {
            return YOUNG;
        }
        if ("MarkSweepCompact".equals(gcName) || "PS MarkSweep".equals(gcName) || "ConcurrentMarkSweep".equals(gcName) || "G1 Old Generation".equals(gcName)) {
            return OLD;
        }
        return defaultName;
    }           

上面的代碼中,你會看到很多我們在上一節中提到的 GC 算法的名稱。

至此,源碼相關部分也講解完畢,感興趣的大家可以自行去查閱。

4. 總結

講解 GC 的文章已經很多,本文又唠唠叨叨地講一遍基礎知識,是希望對于第一次了解 GC 的同學有所幫助。因為隻有了解了這些基礎知識,你才不至于被這些 GC 的輸出吓懵。希望本文對你了解 ES 的 GC 日志 有所幫助。

5. 參考資料

  1. Java Hotspot G1 GC的一些關鍵技術( https://mp.weixin.qq.com/s/4ufdCXCwO56WAJnzng_-ow
  2. Understanding Java Garbage Collection( https://www.cubrid.org/blog/understanding-java-garbage-collection
  3. 《深入了解Java虛拟機:JVM進階特性與最佳實踐》
聲明:本文由原文《你看懂 Elasticsearch Log 中的 GC 日志了嗎?》作者“魏彬”授權轉載,對未經許可擅自使用者,保留追究其法律責任的權利。
【最佳實踐】一文掌握并應用Elasticsearch中的GC實作垃圾日志處理

阿裡雲Elastic Stack

】100%相容開源ES,獨有9大能力,提供免費X-pack服務(單節點價值$6000)

相關活動

更多折扣活動,請

通路阿裡雲 Elasticsearch 官網 阿裡雲 Elasticsearch 商業通用版,1核2G ,SSD 20G首月免費 阿裡雲 Logstash 2核4G首月免費
【最佳實踐】一文掌握并應用Elasticsearch中的GC實作垃圾日志處理
【最佳實踐】一文掌握并應用Elasticsearch中的GC實作垃圾日志處理