歡迎光臨
每天分享高質量文章

雜談 GC

(點選上方公眾號,可快速關註)


來源:佔小狼,

www.jianshu.com/p/2750c7c202ef

上週有幸給部門的小夥伴分享了一些JVM相關的知識,在整個做PPT的過程中,也是對一個領域的碎片知識的整理,本文將針對虛擬機器GC相關的一些內容進行整理,本文不會涉及到G1收集器。

在Hotspot VM實現中,主要有兩大類GC

1. Partial GC:並不會堆整個GC堆進行收集

  • young gc:只收集 young gen 的GC

  • old gc:只收集 old gen 的GC,只有CMS的 concurrent collection

2. mixed GC:收集整個 young gen 以及部分 old gen 的GC,只有G1

Full GC:收集整個堆,包括young gen、old gen、perm gen(如果存在的話)等

其實在各種文章或書上還可以看到Minor GC、Major GC的字眼,其中minor GC和young gc對應,而Major GC通常是和Full GC是等價的,由於HotSpot VM發展了這麼多年,外界對各種名詞的解讀已經完全混亂了,所以Major GC有時也可能是指old gc,在下定論之前一定要先問清楚。

單執行緒、並行、併發

在GC收集器實現中,分為了單執行緒、並行和併發。

  • 單執行緒收集器:如 Serial GC,這個比較好理解,即垃圾收集過程中只有單一執行緒在進行收集工作,實現也最簡單。

  • 並行收集器:如Parallel GC,每次執行時,不管是YGC,還是FGC,會 stop-the-world,暫停所有的使用者執行緒,並採用多個執行緒同時進行垃圾收集。

  • 併發收集器:如CMS GC,在新生代進行垃圾收集時和並行收集器類似,都是並行收集(當然具體演演算法中,你也可以設定成採用單執行緒進行收集),而且都會stop-the-world,主要的區別在於老年代的收集上,CMS在老年代進行垃圾收集時,大部分時間可以和使用者執行緒併發執行的,只有小部分的時間stop-the-world,這就是它的優勢,可以大大降低應用的暫停時間,當然也是有劣勢的。

演演算法組合

Hotspot VM實現的幾種GC演演算法組閤中,其中CMS GC使用最廣,因為現在都是大記憶體時代。

1、Serial GC

Serial generational collector (-XX:+UseSerialGC)

是全域性範圍的Full GC,這種演演算法組合是最早出現的,當年的Java堆記憶體大小都還不大,使用Serial GC進行單執行緒收集,還感覺不出來GC耗時導致應用暫停的問題

2、Parallel GC

Parallel for young space, serial for old space generational collector (-XX:+UseParallelGC).

Parallel for young and old space generational collector (-XX:+UseParallelOldGC)

當Java堆慢慢變大時,發現已經無法忍受GC耗時帶來的應用暫停了,出現了Parallel GC,採用多執行緒的方式進行垃圾收集,很明顯可以提升垃圾收集效率。

3、CMS GC

Concurrent mark sweep with serial young space collector (-XX:+UseConcMarkSweepGC

–XX:-UseParNewGC)

Concurrent mark sweep with parallel young space collector (-XX:+UseConcMarkSweepGC)

當Java堆達到更大時,比如8G,使用Parallel GC帶來的應用暫停已經很明顯了,所有又出現了 CMS GC,這是目前我看到線上環境使用的比較多的GC策略,在引數中新增-XX:+UseConcMarkSweepGC,對於 young gen,會自動選用 ParNewGC,不需要額外新增 -XX:+UseParNewGC。

CMS雖然好,因為它的特殊演演算法,大部分的收集過程可以和使用者執行緒併發執行,大大降低應用的暫停時間,不過也會帶來負面影響,在收集完 old gen 之後,CMS並不會做整理過程,會產生空間碎片,如果這些碎片空間得不到利用,就會造成空間的浪費,整個過程中可能發生 concurrent mode failure,導致一次真正意義的 full gc,採用單執行緒對整個堆(young+old+perm) 使用MSC(Mark-Sweep-Compact)進行收集,這個過程意味著很慢很慢很慢,而且這個碎片問題是無法預測的.

4、G1 GC

G1 garbage collector (-XX:+UseG1GC),本文不對G1進行介紹

觸發條件

young gc

對於 young gc,觸發條件似乎要簡單很多,當 eden 區的記憶體不夠時,就會觸發young gc,我們看看在 eden 區給物件分配一塊記憶體是怎樣一個過程,畫了一個簡單的流程圖,我一直覺得一個好的示意圖可以讓一個枯燥的過程變得更有意思。

在 eden 區分配空間記憶體不足時有兩種情況,為物件分配記憶體、為TLAB分配記憶體,總之就是記憶體不夠,需要進行一次 young gc 為eden區騰出空間為後續的記憶體申請做準備,然後由一個使用者執行緒通知VM Thread,接下去要執行一次 young gc。

full gc

1、old gen 空間不足

當建立一個大物件、大陣列時,eden 區不足以分配這麼大的空間,會嘗試在old gen 中分配,如果這時 old gen 空間也不足時,會觸發 full gc,為了避免上述導致的 full gc,調優時應儘量讓物件在 young gc 時就能夠被回收,還有不要建立過大的物件和陣列。

2、統計得到的 young gc 晉升到 old gen的物件平均總大小大於old gen 的剩餘空間

當準備觸發一次 young gc時,會判斷這次 young gc 是否安全,這裡所謂的安全是當前老年代的剩餘空間可以容納之前 young gc 晉升物件的平均大小,或者可以容納 young gen 的全部物件,如果結果是不安全的,就不會執行這次 young gc,轉而執行一次 full gc

3、perm gen 空間不足

如果有perm gen的話,當系統中要載入的類、反射的類和呼叫的方法較多,而且perm gen沒有足夠空間時,也會觸發一次 full gc

4、ygc出現 promotion failure

promotion failure 發生在 young gc 階段,即 cms 的 ParNewGC,當物件的gc年齡達到閾值時,或者 eden 的 to 區放不下時,會把該物件複製到 old gen,如果 old gen 空間不足時,會發生 promotion failure,並接下去觸發full gc

在GC日誌中,有時會看到 concurrent mode failure 關鍵字,這是因為什麼原因導致的問題呢? 對這一塊的理解,很多文章都是說因為 concurrent mode failure 導致觸發full gc,其實應該反過來,是full gc 導致的 concurrent mode failure,在cms gc的演演算法實現中,通常說的cms是由一個後臺執行緒定時觸發的,預設每2秒檢查一次old gen的記憶體使用率,當 old gen 的記憶體使用率達到-XX:CMSInitiatingOccupancyFraction設定的值時,會觸發一次 cms gc,對 old gen 進行併發收集,而真正的 full gc 是透過 vm thread執行緒觸發的,而且在判斷當前ygc會失敗的情況下觸發full gc,如上一次ygc出現了promotion failure,如果執行 full gc 時,發現後臺執行緒正在執行 cms gc,就會導致 concurrent mode failure。

對於以上這些情況,CMSInitiatingOccupancyFraction引數的設定就顯得尤為重要,設定的太大的話,發生CMS時的剩餘空間太小,在ygc的時候容易發生promotion failure,導致 concurrent mode failure 發生的機率就增大,如果設定太小的話,會導致 cms gc 的頻率會增加,所以需要根據應用的需求對該引數進行調優。

5、執行 System.gc()、jmap -histo:live 、jmap -dump …

參考資料

  • Major GC和Full GC的區別是什麼?觸發條件呢

    https://link.jianshu.com/?t=https://www.zhihu.com/question/41922036/answer/93079526

看完本文有收穫?請轉發分享給更多人

關註「ImportNew」,提升Java技能

贊(0)

分享創造快樂