天天看點

JVM垃圾回收器詳解:并發标記清除算法概述及并發算法觸發時機

作者:大資料架構師

标記清除算法概述

标記清除的思路非常簡單:基于連結清單式的記憶體管理方式,标記所有記憶體空間中的活躍對象,同時記錄這些活躍對象。在标記完成後,周遊整個記憶體空間,如果發現記憶體塊中的對象是活躍對象,則不處理;如果發現記憶體塊中的對象是死亡對象,則将記憶體塊放入空閑清單中供後續配置設定使用。

CMS老生代的回收将标記清除算法進行了優化,減少了垃圾回收導緻的停頓時間。其思路是将标記和清除盡可能地并發化,算法被設計為:初始标記、并發标記、再标記和清除等步驟。

1)初始标記(Initial Mark):從根集合出發,标記老生代中的活躍對象。在初始标記中僅僅找到根集合中的引用對象,并不繼續遞歸周遊這些對象的成員變量,而是把這些對象作為并發标記的輸入,使用額外的資料結構(标記位圖)記錄活躍對象。

2)并發标記(Concurrent Mark):根據初始标記的輸入,對活躍對象周遊成員變量,根據對象之間的引用關系找到所有活躍對象。

3)預清理和可終止預清理(preClean&abortable-preClean):在并發标記過程中,對象的引用關系可能發生變化,為了保證标記的正确性,可使用卡表等資料結構來記錄需要再次标記的對象。但是并發标記過程中可能産生了大量需重新标記的對象,是以引入預清理和可終止預清理步驟,盡量将需要重新标記的對象進行并發标記。

4)再标記(Remark/Final Mark):由于并發标記、并發預清理、并發可終止預清理都有可能引入需要重新标記的對象,是以必須引入一個暫停階段,停止Mutator修改對象引用關系,并且在該階段再次從根集合出發,标記老生代中的活躍對象。由于大量的對象已經完成了标記,是以再标記時一般隻需要通路對象的标記狀态而不需要重新做标記動作,是以停頓時間一般不會太長。注意,再标記時會完全周遊對象的引用關系,不會因為某一個對象被标記過就不周遊其成員變量。至此,老生代中所有的活躍對象都會在标記位圖中。

5)并發清除(Concurrent Sweep):周遊老生代,根據标記位圖的資訊,如果對象已經死亡(沒有标記),則回收其占用的記憶體,如果對象活躍,則不處理。需要注意的是,并發清除中Mutator可能在老生代中配置設定新的對象,這些對象需要被識别處理并作為活躍對象(簡單的處理方法為,當在老生代中配置設定對象時,會在标記位圖中記錄新配置設定的對象)。

6)并發複位(Concurrent Reset):最後嘗試擴充老生代、重新複位和清除一些資料結構(例如清除标記位圖),友善下一次執行垃圾回收。

關于如何保證并發标記的正确性在前文已經讨論過,這裡不贅述。下面詳細介紹一下每一步完成的工作,以及實作過程中的一些細節問題。

并發算法觸發時機

老生代的回收有主動回收也有被動回收。其中主動回收也稱為背景GC,它由CMS控制線程判斷是否需要觸發。與背景GC對應的是前台GC,前台GC是指在背景GC發生時又從Mutator觸發了GC,因為前台GC觸發時需要與背景GC進行互動,是以行為更為複雜。

我們先來研究背景GC,再來介紹前台GC。背景GC隻有滿足一定的條件時才會嘗試觸發,其前提條件如下:

1)參數CMSWaitDuration大于等于0時(預設值為2000),表示當間隔CMSWaitDuration毫秒無任何GC或者在間隔時間内有Minor GC執行,可以嘗試觸發。

2)如果參數CMSWaitDuration小于0,則每隔CMSCheckInterval毫秒可以嘗試觸發,參數CMSCheckInterval的預設值為1000。

是否能觸發,還需要看是否滿足下面的條件,隻有滿足下面的條件時才會真正觸發回收,否則直接放棄回收。主要的觸發條件如下:

1)如果發現了Mutator的代碼中通過GC Locker或者System GC等方式要求觸發并發回收,則直接執行。注意,這兩種行為通過參數GCLockerInvokesConcurrent和ExplicitGCInvokesConcurrent控制,預設情況下均為false,即不會觸發。

2)參數UseCMSInitiatingOccupancyOnly(預設為false)為false時,推斷并發回收執行完時是否用盡老生代空間,即老生代記憶體是否已經緊張。

如果推斷記憶體已經緊張,則會直接觸發背景GC;如果推斷記憶體并不緊張,但是記憶體的使用超過了一定的門檻值(由參數CMSBootstrapOccupancy控制,預設值是50,表示老生代的50%空間),也會直接觸發。

3)如果老生代記憶體的使用超過一定門檻值也會觸發,門檻值的計算方式如下。

CMSInitiatingOccupancyFraction設定為大于0,表示老生代空間的使用超過該門檻值時直接觸發。預設值為-1,表示并不使用該規則。

JVM垃圾回收器詳解:并發标記清除算法概述及并發算法觸發時機

其中MinHeapFreeRatio預設值為40,CMSTriggerRatio預設值為80,得到預設值為92。

4)如果CMSInitiatingOccupancyFraction為false,并且發現擴充記憶體可能導緻的GC時,也會觸發,否則忽略。

5)如果已經發生Minor GC晉升失敗或者發現老生代的剩餘空間不足以滿足下一次Minor GC的晉升時也會觸發。

6)中繼資料發生過配置設定失敗,但還沒有觸發過GC,此時也會觸發老生代回收。

7)以上條件均不滿足時,如果參數CMSTriggerInterval設定為大于0,則會計算從上次回收到現在的時間間隔是否大于該參數,如果大于則執行。

參數CMSTriggerInterval的預設值為-1,表示規則不生效。

從背景GC觸發的規則可以看出,JVM設計者還是想盡量避免使用者主動調參,希望通過預測機制來觸發GC的執行。但是在一些特殊場景中,Mutator配置設定請求的突變可能導緻預測方法失效,在這種情況下,使用者可以通過設定相關的參數盡早地觸發背景GC。

本文給大家講解的内容是VM垃圾回收器詳解:并發标記清除回收,并發的老生代回收-标記清除算法概述及并發算法觸發時機

  1. 下篇文章給大家講解的内容是JVM垃圾回收器詳解:并發标記清除回收,并發的老生代回收-并發标記清除之初始标記、并發标記
  2. 感謝大家的支援!

繼續閱讀