垃圾收集器:利用垃圾收集演算法,實現垃圾回收的實踐落地。
1 HotSpot垃圾回收器
HotSpot垃圾回收器有多個,可以配合使用。
1.1 垃圾回收的一些術語
術語: Stop the world
簡寫為STW,也叫全域性停頓,Java程式碼停止執行,native程式碼繼續執行,但不能與JVM進行互動。
STW主要是為了GC操作的準確性和效率。使用者執行緒不停止的話,會不斷有新物件和垃圾物件產生,假設沒有STW,會導致GC時間過長,錯誤清理新物件等情況。
原因:多半由於垃圾回收導致;也可能是Dump執行緒、死鎖檢查、Dump堆等導致。
危害:服務停止、沒有響應;主從切換(對於高可用環境,如果停頓時間過長,會引發主從之間的切換)、危害生產安全。
因此,儘量縮短Stop the world的時間。
術語--並行收集 VS 併發收集
並行收集:指多個垃圾收集執行緒並行工作,但是在收集的過程中,使用者執行緒(你的業務執行緒)還是處於等待狀態的
併發收集:指使用者執行緒與垃圾收集執行緒同時工作
術語-- 吞吐量
CPU用於執行使用者程式碼的時間與CPU總消耗時間的比值
公式:執行使用者程式碼時間/(執行使用者程式碼時間+垃圾收集時間)
2 新生代垃圾收集器(與老年代收集器配合使用)
2.1 Serial收集器
Serial 收集器是最基本、發展歷史最悠久的收集器,採用複製演算法。採用單執行緒操作,收集過程中全程STW。
適用場景有:
- 客戶端程式,應用以
-client
模式執行時,預設使用的就是Serial(java -client -jar
執行) - 單核機器
2.2 ParNew收集器
Serial收集器的多執行緒版,除使用多執行緒以外,其他和Serial收集器一樣,包括:JVM引數、Stop the world表現、垃圾收集演算法都是一樣的。可使用 -XX:ParallelGCThreads
設定垃圾收集的執行緒數。主要和CMS垃圾收集器配合使用。
2.3 ParallelScavenge收集器
ParallelScavenge收集器,是吞吐量優先收集器,也是採用複製演算法,也是多執行緒的。執行過程與ParNew收集器類似。
Parallel Scavenge收集器適用於注重吞吐量的場景。
Parallel Scavenge收集器特點
可以達到一個可控制的吞吐量
-
-XX:MaxGCPauseMillis
:控制最大的垃圾收集停頓時間(盡力) -
-XX:GCTimeRatio
:設定吞吐量的大小,取值0-100,系統花費不超過1/(1+n)的時間用於垃圾收集
自適應GC策略:可用-XX:+UseAdptiveSizePolicy
開啟
- 開啟自適應策略後,無需手動設定新生代的大小(-Xmn)、Eden與Survivor區的比例(-XX:SurvivorRatio)等引數虛擬機器會自動根據系統的執行狀況收集效能監控資訊,動態地調整這些引數,從而達到最優的停頓時間以及最高的吞吐量。
3 老年代收集器(與新生代收集器配合使用)
3.1 Serial Old收集器
Serial Old收集器是Serial收集器的老年代版本,單執行緒,採用的是標記-整理演算法,垃圾收集過程Stop The World。
可以與上面三個新生代收集器配合使用;當CMS收集器出現故障時,作為後備處理器。
3.2 Parallel Old收集器
Parallel Old收集器是Parallel Scavenge收集器的老年代版本,只能與Parallel Scavenge收集器配合使用,同樣是注重對吞吐量要求較高的場景。
Parallel Old收集器,採用多執行緒,標記-整理演算法,垃圾收集過程Stop The World。
3.3 CMS收集器(JDK9廢棄)
CMS: Concuerrent mark sweep(併發標記收集器),採用標記-清除演算法。
CMS執行過程的七個階段:
- 初始標記:標記GC Roots能直接關聯的物件,觸發短暫STW操作。
- 併發標記:找出所有GC Roots能關聯的物件。併發執行,不會觸發STW。
- 併發預清理階段(可選):重新標記在併發標記階段引用被更新的物件,從而減少後面重新標記的工作量。併發執行,不會觸發STW。可使用
-XX:-CMSPrecleaningEnabled
關閉併發預清理階段,預設開啟。 - 併發可中止的預清理階段(可選):和併發預清理做的事情一樣,併發執行,無 Stop The World。預清理後,當Eden的使用量大於
CMSScheduleEdenSizeThreshold
的閾值(預設2M)時,才會執行該階段。主要作用是允許我們能夠控制預清理階段的結束時機。 - 重新標記階段:修正併發標記期間,因為使用者程式繼續執行,導致標記發生變動的那些物件的標記。一般來說,重新標記花費的時間會比初始標記階段長一些,但比並發標記的時間短。存在 Stop The World。
- 併發清除階段:基於標記結果,清除垃圾物件。併發執行,無STW。使用標記清除演算法,因為標記整理涉及資料位置,併發情況難以實現。
- 併發重置階段:清除本地CMS GC的上下文資訊,為下次GC做準備。
CMS收集器的優缺
優點:STW時間較短,大多數過程併發執行。
缺點:
- 垃圾回收併發執行時,會與使用者執行緒有一定CPU資源爭搶,影響吞吐量;
- CMS只在標記的時候STW,清除時無STW,無法處理浮動垃圾;
- 採用標記-清除法,導致記憶體碎片的產生;
- 可以使用
UseCMSCompactAtFullCollection
:在完成Full GC後是否要進行記憶體碎片整理,預設開啟。CMSFullGCsBeforeCompaction
:進行幾次Full GC後就進行一次記憶體碎片整理,預設是0。
- 可以使用
- 無法等到老年代幾乎滿了才開始垃圾收集。
- 執行過程中多個階段沒有STW操作,會不斷有物件晉升到老年代,當老年代預留的記憶體不夠時,會導致Concurrent Mode Failure,從而切換成後備老年代收集器 Serial Old。
- 可使用
CMSInitiatingOccupancyFraction
設定老年代佔比達到多少就觸發垃圾收集,預設68%。
CMS適用於希望系統停頓時間短,響應速度快的場景,例如Web。
擴充套件:併發預處理、併發中斷預處理
1、首先,CMS是一個關注停頓時間,以回收停頓時間最短為目標的垃圾回收器。併發預處理階段做的工作是標記,重標記需要STW(Stop The World),因此重標記的工作儘可能多的在併發階段完成來減少STW的時間。此階段標記從新生代晉升的物件、新分配到老年代的物件以及在併發階段被修改了的物件。
2、併發可中斷預清理(Concurrent precleaning)是標記在併發標記階段引用發生變化的物件,如果發現物件的引用發生變化,則JVM會標記堆的這個區域為Dirty Card。那些能夠從Dirty Card到達的物件也被標記(標記為存活),當標記做完後,這個Dirty Card區域就會消失。CMS有兩個引數:CMSScheduleRemarkEdenSizeThreshold、CMSScheduleRemarkEdenPenetration,預設值分別是2M、50%。兩個引數組合起來的意思是預清理後,eden空間使用超過2M時啟動可中斷的併發預清理(CMS-concurrent-abortable-preclean),直到eden空間使用率達到50%時中斷,進入重新標記階段。
4 G1收集器
G1(Garbge First),面向服務端應用,可以同時用於新生代和老年代。
G1收集器使用Region作為單位,包括四種型別,分別為Eden、Survior、Old和Humongous,通過引數-XX:G1HeapRegionSize
指定Region的大小,取值範圍為1MB ~ 32 MB之間。
前三種Region仍然是伊甸園、存活區、老年代,Humongous用來儲存大物件,物件過大可以儲存到連續的Humongous中。Old和Humongous同屬於老年代。
G1收集器的設計思想:將記憶體分成很多小塊(Region),跟蹤每個Region中垃圾堆積的價值大小,構建一個優先列表,根據允許的收集時間,優先回收價值最高的Region。其中價值大小指的是回收該Region能獲得的空間大小以及回收所需要的時間成本。
4.1 G1收集器的垃圾回收機制
主要分為三種,Young GC、Mixed GC、Full GC。
4.1.1 Young GC
所有Eden Region都滿了的時候,就會觸發Young GC。
- Eden中存活的物件轉移到Survior Region;
- 原先 Survivor Region中的物件轉移到新的 Survivor Region中,或者晉升到Old Region。
- 空閒 Region會被放入空閒列表中,等待下次被使用。
4.1.2 Mixed GC
老年代大小佔整個堆的百分比達到一定閾值(可用-XX:InitiatingHeapOccupancyPercent指定,預設45%),就觸發Mixed GC,會回收所有 Young Region,同時回收部分 Old Region。
執行過程分為四步:
-
初始標記:跟CMS類似,標記處GC Roots能直接關聯到的物件,存在短暫STW。
-
併發標記:跟CMS類似,找出所有GC Roots能關聯的物件。併發執行,不會觸發STW。
-
最終標記:更新在併發標記期間引起的變更,存在STW。
-
篩選回收:首先對各個Region的回收價值和成本排序,根據使用者所期望的停頓時間指定回收計劃,篩選出合適的Region回收。停頓時間:MaxGCPauseMillis。
回收過程:將Region的存活物件複製到空閒Region中,然後刪除原Region,是複製演算法,無記憶體碎片,過程存在STW。
G1收集器Mixed GC除了併發標記以外的過程都是STW的,由於一次只回收一部分Region,所以停頓時間可控。
4.1.3 Full GC
複製物件記憶體不夠,或者無法分配足夠的記憶體(例如大物件無法分配連續的記憶體),就會觸發Full GC。Full GC機制採用單執行緒的Serial Old模式。
因此G1收集器的優化原則是,儘可能的減少Full GC。
4.2 G1優化原則:減少Full GC
- 增加預留記憶體(增大-XX:G1 Reserve Percent,預設為堆的10%)
- 更早地回收垃圾(減少- XX: InitiatingHeapOccupancyPercent,老年代達到該值就觸發 Mixed GC,預設45%
- 增加併發階段使用的執行緒數(增大-XX: ConcGCThreads)
上面三點大白話總結:多留一點記憶體,有記憶體可以分配;有垃圾早點回收;垃圾回收快點。
4.3 G1收集器的使用
G1收集器作用於整個堆,可以控制停頓時間(MaxGCPauseMillis=200),並且沒有記憶體碎片。
G1收集器佔用記憶體較大(通常需要6G以上),可以代替CMS收集器。對於JDK8,主要根據記憶體選擇,記憶體小於6G,選CMS;記憶體大於6G,使用G1。CMS在JDK9中被廢棄,高於JDK8的版本可以選G1。
5 實驗收集器
截止目前JDK14依然處於實驗狀態
-
Shenandoah(IBM開發,是ZGC競品,進入OpenJDK,被Oracle JDK12剔除。)
-
ZGC(一款革命性的收集器)
-
Epsilon:不幹活的垃圾收集器。
- 控制記憶體分配,但是不執行垃圾回收工作,堆耗盡就直接關閉JVM。
6 如何選擇垃圾收集器?
從理論出發主要有下面幾點。
- 專案主要關注矛盾點。例如吞吐量(Parallel)、訪問延遲(CMS/G1)、應用啟動速度
- 基礎設施:CPU、記憶體等
- JDK版本。例如JDK6沒有G1,Oracle JDK沒有Shenandoah。