1. 程式人生 > 程式設計 >JVM(三)-- 垃圾回收

JVM(三)-- 垃圾回收

一:風騷概述

JVM中垃圾收集演演算法主要有複製演演算法、標記--清除、標記--整理、分代收集,每種垃圾收集器可以說都是一種或多種垃圾收集演演算法的實現。堆空間分代、垃圾收集器、垃圾收集演演算法三者的關係可以用以下特點概括:

  • 堆空間:不同物件根據存活時間記憶體大小等特點分佈在堆不同空間
  • 收集演演算法:不同堆空間物件回收率等存在明顯差異,選用不同特點垃圾收集演演算法
  • 收集器:收集演演算法例項即垃圾收集器

二:回收演演算法

不同垃圾收集演演算法具備不同的特點,對於每塊堆記憶體空間來講都有比較符合儲存物件特點的垃圾收集演演算法。主要介紹複製演演算法、標記 -- 清除、標記 -- 整理、分代收集演演算法

2.1 複製演演算法

在這裡插入圖片描述

  • 實現原理:將所有存活物件複製一份放入一塊記憶體,然後將區域外所有記憶體回收
  • 適用區域:堆空間新生代物件朝生夕死,存活物件比例低
  • 演演算法缺點:需要使用一塊記憶體區域作為複製物件存放區域,記憶體使用率低
  • 新生代優化:新生代朝生夕死存活率低,將記憶體預設分為8:1:1,預設使用1/10的S區域存放,避免大量記憶體浪費。同時使用分配擔保策略減少OOM風險
2.2 標記 -- 清除演演算法

在這裡插入圖片描述

  • 實現原理:標記所有可回收物件將其進行回收
  • 適用區域:只有CMS收集器採用這個演演算法收集老年代
  • 演演算法缺點:容易造成記憶體碎片導致物件尋找不到合適記憶體從而誘發GC
  • 演演算法優點:實現簡單,不需要挪動存活物件位置,在物件存活率較高場景下比較優秀
2.3 標記 -- 整理演演算法

在這裡插入圖片描述

  • 實現原理:標記可回收物件、將存活物件移動到連續記憶體區域、回收區域外記憶體
  • 適用區域:大部分老年代收集器採用演演算法
  • 演演算法缺點:在標記 -- 清除演演算法基礎上增加物件挪動所以成本更高
  • 演演算法優點:相對於標記 -- 清除解決了碎片記憶體問題,相對於複製演演算法避免了預留區域的問題。一定注意該演演算法是先標記 --> 再移動整合 --> 最後收集這個時間操作軸
2.4 分代收集演演算法

這塊內容留到G1垃圾收集器再專門介紹,簡單理解就是收集器根據不同記憶體區域採用不同演演算法實現垃圾回收

三:垃圾收集器

在這裡插入圖片描述
根據不同垃圾收集演演算法實現的垃圾收集器具備不同特點,當然也就運用於不同記憶體區域。其中新生代垃圾收集器包括Serial、ParNew、Parallel Scavenge,老年代垃圾收集器Serial Old、Parallel Old、CMS以及分代收集器G1等。不同的垃圾收集器可以配合使用,根據不同的需求場景可以選擇最合適的配合

3.1 Serial / Serial Old

在這裡插入圖片描述

  • Serial採用複製演演算法的新生代單執行緒垃圾收集器
  • Serial可配合老年代Serial Old、CMS進行垃圾收集
  • Serial是Client模式下預設垃圾收集器
  • Serial Old是Serial老年代實現採用標記 -- 整理演演算法,除了可以配合所有新生代垃圾收集器外還可以作為CMS的後備垃圾收集器
3.2 ParNew

在這裡插入圖片描述

  • 採用複製演演算法的新生代並行垃圾收集器,可看做為Serial的多執行緒版本
  • 可配合Serial Old、CMS進行垃圾回收工作
3.3 Parallel Scavenge / Parallel Old

在這裡插入圖片描述

  • 採用複製演演算法的新生代並行收集器,關注吞吐量。即垃圾收集與執行緒執行時間比例
  • 可配合Serial Old、Parallel Old實現垃圾回收,與Parallel Old組成一組吞吐量優先的垃圾收集器
  • 吞吐量設定引數-XX:MaxGCPauseMillis指定垃圾收集最大停頓毫秒數時間、引數-XX:GCTimeRatio指定GC執行緒與使用者執行緒執行時間所佔最大比例
  • 與ParNew最大的一個區別在於自適應策略,引數-XX:+UseAdaptiveSizePolicy開啟。自適應解釋為虛擬機器器控制新生代、老年代大小比例以及Eden、From、To區域比例
  • Parallel Old是Parallel Scavenge的老年代版本實現,採用標記 -- 整理演演算法。一組關注吞吐量的垃圾回收器
3.4 CMS

在這裡插入圖片描述

  • 垃圾收集劃分為四個階段,初始標記 --> 併發標記 --> 重新標記 --> 併發清除。初始標記僅僅標記GCRoot節點、重新標記僅僅修正併發標記階段使用者執行緒執行導致的物件關係變化。所以整體上來看CMS在這兩個階段雖然有STW但是影響不大,可以稱得上併發收集器
  • CMS採用標記 -- 清除垃圾回收演演算法,導致回收後產生大量記憶體碎片,可能會導致頻繁的發生GC
  • CMS是一款併發的垃圾收集器,也就是使用者執行緒還在持續執行。垃圾收集不能等到記憶體空間100%使用再進行,需要預留部分記憶體供使用者執行緒使用
  • 當併發進行的GC執行緒回收速度跟不上使用者執行緒垃圾產生速度,這時就會提示Concurrent Mode Failure從而啟動後備垃圾收集器Serial Old進行STW的垃圾回收
    在這裡插入圖片描述
3.5 G1

在這裡插入圖片描述

  • 首先需要明確G1垃圾收集器記憶體劃分採用化整為零思想,一個個獨立的Regin區域組成整個GC堆。新生代、老年代概念僅僅標識某些可以物理上不連續的Regin集合

    在這裡插入圖片描述

  • G1收集器還有一個比較重要的特徵就是可預測停頓,這點與關注吞吐量的Parallel收集器類似。即在M時間段內可設定使用者垃圾回收的時長不超過N,具體實現原理就是將所有Region的垃圾收集維護一個優先順序表,GC時計算出N時長內效率最高的一些Region進行回收

  • 當某個物件位於Region1,引用該物件的物件位於Region2,那麼在可達性演演算法分析時進行全堆掃描?G1中為每個Region維護一個Remembered Set,當其它引用物件對該某個物件物件產生引用關係時就會在引用物件的Remembered Set中記錄,保證了引用關係標記的準確性

  • 除了初始標記與併發標記與CMS一致外,G1後兩個階段為最終標記與篩選回收。篩選回收也就是根據上面講的優先順序列表進行,該階段會STW並行執行,且回收演演算法帶有記憶體整合。最終標記就是將在併發標記階段使用者執行緒記錄到Remembered Set logs的引用關係合併到Remembered Set中,修正標記結果

四:垃圾收集引數

垃圾收集器具備多種搭配,每種垃圾收集器又都有一些自己獨特的配置。所以針對這兩點,將從垃圾收集器選擇引數以及收集器配置兩方面講解一些常用引數

4.1 Serial + SerialOld

這套垃圾收集器組合是Client模式下預設的垃圾收集器配置,若在Server模式下想強制指定該配置則使用引數-XX:+UseSerialGC即可。使用該組合垃圾收集器後新生代用DefNew 表示,老年代使用Tenured表示

在這裡插入圖片描述

4.2 ParNew + Serial Old

相對於Serial來講ParNew基本可以說是多執行緒版本,同時它還是出了Serial之外唯一可以與CMS合作的新生代垃圾收集器。使用引數-XX:UseParNew強制指定,使用ParNew垃圾收集器後新生代使用ParNew標記

在這裡插入圖片描述

4.3 Parallel Scavenge + Parallel Old

關注吞吐量,JDK1.8預設的垃圾收集器組合。使用引數+XX:UseParallelGC強制指定,前面講過相對於ParNew來講,Parallel Scavenge還有最重要的特徵就是堆記憶體分配自適應調節策略,使用引數-XX:UseAdaptiveSizePolicy引數開啟。只需要設定最大最小堆,新生代老年代大小比例,新生代Eden、S區域比例,晉升老年代年齡等引數都會自動設定。這套組合的新生代用PSYoungGen、老年代用ParOldGen表示。當然吞吐量相關引數:

  • -XX:GCTimeRatio:0-100的整數,1 / (1 + GCTimeRatio) = 垃圾回收時間佔比
  • -XX:MaxGCPauseMillis : GC使用最長時間,單位毫秒
    在這裡插入圖片描述
4.4 ParNew + CMS + Serial

使用引數-XX:+UseConcMarkSweepGC強制指定垃圾收集器,需要注意的是這裡的Serial僅僅只是一個後備的垃圾收集器而已。當然使用CMS後老年代使用CMS表示,比較重要的一點就是CMS採用標記 -- 清除演演算法有可能會導致重複頻繁的GC。為了控制這種情況,CMS提供引數-XX:CMSFullGCsBeforeCompaction指定多少次Full GC後進行一次記憶體整理

在這裡插入圖片描述

五:GC發生時間

講完GC大部分相關內容後總得清楚什麼時候發生GC這個操作呀,首先需要明確的是GC分為新生代的Minor GC 以及 老年代的Full GC。那麼這兩種GC到底在什麼時候觸發呢?

5.1 Minor GC

當新生代剩餘連續記憶體不足以分配新生物件,老年代剩餘空間滿足分配擔保策略需求的時候會執行Minor GC,如若不然執行Full GC

在這裡插入圖片描述

5.2 Full GC

除了上述說的因為Minor GC引發的Full GC之外,還有什麼情況會導致Full GC的發生?如下所示:

  1. 顯示呼叫System.gc(),一般不會做這麼蠢的操作
  2. 大物件直接晉入老年代導致的老年代記憶體不足
  3. 方法區記憶體不足導致需要進行類解除安裝的GC