天天看點

HotSpot虛拟機垃圾收集器-理論篇

java的對象執行個體存放在堆裡,java的對象無需程式員管理,而是由垃圾收集器(Garbage Collection 簡稱GC)自動管理。

一般來說,大部分對象都是朝生夕滅的,而熬過越多次垃圾收集過程的對象就越難以消亡。

據此,一般垃圾收集器會根據回收對象的年齡(經曆過垃圾收集過程的次數),将自身管理的堆分為新生代(Young Generation)和老年代(Old Generation)。每次回收優先回收新生代,以便快速回收大量的可用空間。

基礎垃圾收集算法有三種:

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

即掃描記憶體區域,标記所有需要回收的對象,統一回收。這種算法有兩個缺點,一個是如果記憶體中大部分的對象都是要回收的,效率會比較低,另一個是多次清除後,會産生很多不連續的記憶體碎片。碎片太多之後,配置設定稍大一點的對象都會觸發垃圾收集。

标記-複制算法(Semispace Copying)

考慮90%以上的對象都是朝生夕滅的,都熬不過第一輪垃圾回收。可以将新生代分為三塊,一塊Eden,兩塊Survivor,預設比例8:1:1。

配置設定對象時隻使用Eden和一塊Survivor,垃圾回收時,掃描這兩塊記憶體,将存活的對象複制到另一塊Survivor中,然後直接清理調Eden和已使用的那塊Survivor空間。

複制到未使用的Survivor中的存活對象,在熬過特定次數後,再轉移到老年代中。

标記-整理算法(Mark-Compact)

在老年代中,大部分對象都是存活的,根據這個特征,可以将所有的存款對象向記憶體空間一端移動,然後直接清理掉邊界以外的記憶體。

由于移動存活對象必須更新所有引用這些對象的地方,一般來說這時必須要全程暫停使用者應用程式。這一點在目前追求快速響應的網際網路時代顯然是一般系統無法忍受的。

基于HotSpot虛拟機的垃圾收集器有:

  • 新生代收集器 Minor GC:

Serial收集器

單線程的垃圾收集器,采用标記複制算法,整個GC過程必須全程停止使用者線程。适用于堆記憶體較小的場景,用戶端模式下的預設新生代收集器

ParNew收集器 Serial的多線程版,激活CMS收集器後的預設新生代收集器,在多核處理器系統中可以高效利率系統資源

Parallel Scavenge收集器

可以通過-XX:MaxGCPauseMillis設定最大停頓時間,或通過-XX:GCTimeRatio設定吞吐量大小,吞吐量=使用者代碼運作時間/(使用者代碼運作時間+垃圾收集運作時間)。

停頓時間設定适用于需要與使用者互動或需要保證服務響應品質的程式,良好的響應速度能提升使用者體驗;而高吞吐量則适合背景運算而不需要太多互動的任務。

該收集器還提供了一個自适應的選項,-XX:+UseAdaptiveSizePolicy,使用該參數後,系統會根據使用者設定的最大停頓時間或最大吞吐量,自動調節新生代大小、Eden與Survivor的比例,晉升老年代的大小等參數對虛拟機進行優化。

  • 老年代收集器 Major GC:

Serial Old收集器

Serial收集器的老年代版本,單線程。CMS失敗的後備預案。

Paralle Old收集器

Parallel Scavenge收集器的老年代版本,支援多線程并發收集,基于标記-整理算法實作。 JDK6之後開始提供。

Concurrent Mark Sweep收集器

基于标記-清除算法的并發低停頓收集器。收集過程分為四步:初始标記、并發标記、重新标記、并發清除,僅在初始标記階段需要停頓使用者線程,其他三步都可以與使用者線程同時進行。

該處理器有三個缺點:

1. 對于四核以下的處理器,會由于占用處理器資源過多,而導緻使用者程式執行速度大幅降低

2. 由于收集過程與使用者線程并發進行,是以在老年代中必須預留一定空間供并發收集時的應用程式運作使用。

預留的空間不夠并發的使用者程式使用時,會出現并發失敗,這時虛拟機會啟用SerailOld對老年代重新進行垃圾收集,這次停頓時間就會很長。

預留白間的比例可以通過-XX:CMSInitiatingOccupancyFraction來設定,設定之後,一旦老年代的使用比例達到門檻值,就會觸發一次CMS GC。

預留比例設定太小,會導緻GC過于頻繁,過大又會導緻大量的并發失敗,是以需要根據生産環境的情況權衡設定。

3. 标記-清除算法會産生大量空間碎片,當碎片過多導緻大對象無法配置設定時,也會觸發FullGC,出現長時間的停頓

  • 混合收集器

Garbage First收集器

簡稱G1收集器

不再劃分固定的分代區域,而是把連續的Java堆劃分為多個大小相同的獨立區域,每個Region可以根據需要,扮演Eden、Survivor或Old Generation。

對于超過Region容量一般的對象,G1判定為大對象,會分出一類特殊的Humongous區域來存放,超過一個Region大小的對象,會被存放在連續的N個Humongous Region中。Humongous區域大多數時候會被當做老年代來處理。

對于所有的Region,G1維護一個優先級清單,這個優先級會根據各個Region裡面垃圾堆積的價值大小來判定,價值即回收所獲得的空間及回收所需時間。

每次回收時,G1會根據使用者設定的允許停頓時間,優先回收那些優先級最高的,價值最大的區域,這樣就能快速收回大量可用空間。

停頓時間通過-XX:MaxGCPauseMillis設定,預設200ms。該值如果設定太小,會導緻每次收集到的記憶體都很少,收集器的速度逐漸跟不上配置設定器配置設定的速度,導緻垃圾慢慢堆積,最終占滿引發FullGC長時間停頓。

按照書中作者的經驗,在小記憶體應用上CMS表現大機率優于G1,而大記憶體上G1會發揮更大的優勢,這個臨界點在6-8G之間。

  • 空收集器

Epsilon收集器

JDK11釋出 是一個空處理器,即不進行垃圾收集。适用于僅需要允許數分鐘乃至數秒就會退出的場景(微服務、無服務),無記憶體管理的運作負載,可最大化提高應用的執行效率。

  • 低延遲收集器

Shenandoah收集器

ZGC收集器

這兩個下周再補充吧,下周順帶會附上我的一些實踐截圖。

本文所有内容來自《深入了解Java虛拟機》一書,屬于讀書筆記。