聊一聊GC(垃圾回收器)

垃圾回收器:
垃圾回收算法是理論垃圾回收器是實現

目前有如下幾種垃圾回收器,連線描述了各個垃圾回收器應用在哪裏,以及相互之間是否可以相互配合使用
在這裏插入圖片描述

新生代的這三個都是複製回收算法
區別在於具體的實現方式

  • 新生代收集器:Serial、ParNew、Parallel Scavenge
  • 老年代收集器:CMS、Serial Old、Parallel Old
  • 整堆收集器: G1

Serial 收集器
Serial收集器是最基本的、發展歷史最悠久的收集器

特點:單線程、簡單高效(與其他收集器的單線程相比),對於限定單個CPU的環境來說,Serial收集器由於沒有線程交互的開銷,專心做垃圾收集自然可以獲得最高的單線程手機效率。收集器進行垃圾回收時,必須暫停其他所有的工作線程,直到它結束(Stop The World)

Serial / Serial Old收集器運行示意圖
在這裏插入圖片描述
適用於單核CPU,因爲單核CPU下,單線程效率更高

ParNew收集器
ParNew收集器其實就是Serial收集器的多線程版本
除了使用多線程外其餘行爲均和Serial收集器一模一樣(參數控制、收集算法、Stop The World、對象分配規則、回收策略等)

  • 特點:多線程、ParNew收集器默認開啓的收集線程數與CPU的數量相同,在CPU非常多的環境中,可以使用-XX:ParallelGCThreads參數來限制垃圾收集的線程數。
       和Serial收集器一樣存在Stop The World問題

  • 應用場景:ParNew收集器是許多運行在Server模式下的虛擬機中首選的新生代收集器,因爲它是除了Serial收集器外,唯一一個能與CMS收集器配合工作的

ParNew/Serial Old組合收集器運行示意圖如下:
在這裏插入圖片描述
適用於多核CPU

Parallel Scavenge 收集器(Java7默認)
Parallel :指的是並行,具體來說是垃圾回收器線程並行執行
與吞吐量關係密切,故也稱爲吞吐量優先收集器,關注的是全局的運行情況,所以沒有具體的時間點

  • 吞吐量:執行用戶代碼時間/(執行用戶代碼時間+垃圾回收使用的時間)

  • 特點:屬於新生代收集器也是採用複製算法的收集器,又是並行的多線程收集器(與ParNew收集器類似)。
    該收集器的目標是達到一個可控制的吞吐量。還有一個值得關注的點是:GC自適應調節策略(與ParNew收集器最重要的一個區別)

  • GC自適應調節策略:Parallel Scavenge收集器可設置-XX:+UseAdptiveSizePolicy參數。當開關打開時不需要手動指定新生代的大小(-Xmn)、Eden與Survivor區的比例(-XX:SurvivorRation)、晉升老年代的對象年齡(-XX:PretenureSizeThreshold)等,虛擬機會根據系統的運行狀況收集性能監控信息,動態設置這些參數以提供最優的停頓時間和最高的吞吐量,這種調節方式稱爲GC的自適應調節策略。

Parallel Scavenge收集器使用兩個參數控制吞吐量:

  • XX:MaxGCPauseMillis 控制最大的垃圾收集停頓時間
  • XX:GCRatio 直接設置吞吐量的大小(0,100)
    如果XX:MaxGCPauseMillis設置的太小,例如1納秒,那麼會導致GC非常頻繁

Serial Old 收集器
Serial Old是Serial收集器的老年代版本

  • 特點:同樣是單線程收集器,採用標記-整理算法
  • 應用場景:主要也是使用在Client模式下的虛擬機中。也可在Server模式下使用

Server模式下主要的兩大用途:
在JDK1.5以及以前的版本中與Parallel Scavenge收集器搭配使用
作爲CMS收集器的後備方案,在併發收集Concurent Mode Failure時使用

Serial / Serial Old收集器工作過程圖(Serial收集器圖示相同):

在這裏插入圖片描述

Parallel Old 收集器
是Parallel Scavenge收集器的老年代版本
Parallel :指的是並行,具體來說是垃圾回收器線程並行執行
特點:多線程,採用標記-整理算法
應用場景:注重高吞吐量以及CPU資源敏感的場合,都可以優先考慮Parallel Scavenge+Parallel Old 收集器。
Parallel Scavenge/Parallel Old收集器工作過程圖:
在這裏插入圖片描述

CMS收集器(老年代默認)
CMS垃圾回收器的全稱是Concurrent Mark-Sweep Collector,從名字上可以看出兩點,一個是使用的是併發收集,第二個是使用的收集算法是Mark-Sweep(標記-清除)
一種以獲取最短回收停頓時間爲目標的收集器

  • 特點:基於標記-清除算法實現。併發收集、低停頓。
  • 應用場景:適用於注重服務的響應速度,希望系統停頓時間最短,給用戶帶來更好的體驗等場景下。如web程序、b/s服務

CMS收集器的運行過程分爲下列4步:

  1. 初始標記:標記GC Roots能直接到的對象。速度很快但是仍存在Stop The World問題。
  2. 併發標記:進行GC Roots Tracing 的過程,找出存活對象且用戶線程可併發執行。
  3. 重新標記:爲了修正併發標記期間因用戶程序繼續運行而導致標記產生變動的那一部分對象的標記記錄。仍然存在Stop The World問題(做的是增量更新,速度很快)
  4. 併發清除:對標記的對象進行清除回收

CMS收集器的內存回收過程是與用戶線程一起併發執行的
CMS收集器的工作過程圖:
在這裏插入圖片描述
CMS收集器的缺點:
對CPU資源非常敏感
無法處理浮動垃圾,可能出現Concurrent Model Failure失敗而導致另一次Full GC的產生(浮動垃圾就是在併發清除的時候,又產生新的垃圾)
因爲採用標記-清除算法所以會存在空間碎片的問題,導致大對象無法分配空間,不得不提前觸發一次Full GC

G1收集器(jdk 9默認)

一款面向服務端應用的垃圾收集器,是標記整理算法

G1(Garbage First)和CMS一樣,也是關注最小時延的垃圾回收器,同樣適合大尺寸堆內存的垃圾收集
G1最大的特點是引入分區的思路,是分區算法的實現,其將整個堆空間劃分爲連續的不同小區間, 每個小區間獨立使用,獨立回收。這樣做的好處是可以控制一次回收多少個小區間 ,根據目標停頓時間,每次合理地回收若干個小區間(而不是整個堆),從而減少一次 GC 所產生的停頓

特點如下:

  • 並行與併發:G1能充分利用多CPU、多核環境下的硬件優勢,使用多個CPU來縮短Stop-The-World停頓時間。部分收集器原本需要停頓Java線程來執行GC動作,G1收集器仍然可以通過併發的方式讓Java程序繼續運行。
  • 分代收集:G1能夠獨自管理整個Java堆,並且採用不同的方式去處理新創建的對象和已經存活了一段時間、熬過多次GC的舊對象以獲取更好的收集效果。
  • 空間整合:G1運作期間不會產生空間碎片,收集後能提供規整的可用內存。
  • 可預測的停頓:G1除了追求低停頓外,還能建立可預測的停頓時間模型。能讓使用者明確指定在一個長度爲M毫秒的時間段內,消耗在垃圾收集上的時間不得超過N毫秒。

G1收集器大致可分爲如下步驟:

  • 初始標記:僅標記GC Roots能直接到的對象,並且修改TAMS(Next Top at Mark Start)的值,讓下一階段用戶程序併發運行時,能在正確可用的Region中創建新對象。(需要線程停頓,但耗時很短。)
  • 併發標記:從GC Roots開始對堆中對象進行可達性分析,找出存活對象。(耗時較長,但可與用戶程序併發執行)
  • 最終標記:爲了修正在併發標記期間因用戶程序執行而導致標記產生變化的那一部分標記記錄。且對象的變化記錄在線程Remembered Set Logs裏面,把Remembered Set Logs裏面的數據合併到Remembered Set中。(需要線程停頓,但可並行執行。)
  • 篩選回收:對各個Region的回收價值和成本進行排序,根據用戶所期望的GC停頓時間來制定回收計劃。(可併發執行)

爲什麼說G1是關注最小時延的垃圾回收器呢?
因爲G1在篩選回收的階段,會根據用戶設置的期望的GC停頓時間來定製回收計劃,例如:
用戶期望每次GC停頓時間不超過10ms,而在篩選回收之前的階段,已經用去了8ms,那麼G1就會選擇回收部分對象,以保證停頓時間不超過10ms

G1收集器運行示意圖:
在這裏插入圖片描述
g1不再區分老年代、年輕代這樣的內存空間,這是較以往收集器很大的差異,所有的內存空間就是一塊劃分爲不同子區域,每個區域大小爲1m-32m,最多支持的內存爲64g左右,且由於它爲了的特性適用於大內存機器。

G1的內存模型
在這裏插入圖片描述
分區 Region
G1採用了分區(Region)的思路,將整個堆空間分成若干個大小相等的內存區域,每次分配對象空間將逐段地使用內存。因此,在堆的使用上,G1並不要求對象的存儲一定是物理上連續的,只要邏輯上連續即可
G1的每個分區不會確定地爲某個代服務,可以按需在年輕代和老年代之間切換
啓動時可以通過參數-XX:G1HeapRegionSize=n可指定分區大小(1MB~32MB,且必須是2的冪),默認將整堆劃分爲2048個分區

卡片 Card
在每個分區內部又被分成了若干個大小爲512 Byte卡片(Card),卡片將會記錄在全局卡片表中,分配的對象會佔用物理上連續的若干個卡片,查找對象時便可通過記錄卡片來查找該引用對象。每次對內存的回收,都是對指定分區的卡片進行處理

在這裏插入圖片描述

ZGC收集器(目前尚無法使用在企業級應用上)
fullGC只需要10ms

Minor GC 和 Full GC的區別

  • 新生代GC(Minor GC):Minor GC指發生在新生代的GC,因爲新生代的Java對象大多都是朝生夕死,所以Minor GC非常頻繁,一般回收速度也比較快。當Eden空間不足以爲對象分配內存時,會觸發Minor GC
    注意young GC一旦發生就是對整個新生代進行GC,而不是僅僅針對Eden區

  • 老年代GC(Full GC/Major GC):Full GC指發生在老年代的GC,出現了Full GC一般會伴隨着至少一次的Minor GC(老年代的對象大部分是Minor GC過程中從新生代進入老年代),比如:分配擔保失敗。Full GC的速度一般會比Minor GC慢10倍以上。當老年代內存不足或者顯式調用System.gc()方法時,會觸發Full GC

如何選擇合適的垃圾收集器

官方建議:

  • 優先調整堆的大小讓服務器自己來選擇
  • 如果內存小於100M,使用串行收集器
  • 如果是單核,並且沒有停頓時間要求,使用串行或JVM自己選
  • 如果允許停頓時間超過1秒,選擇並行或JVM自己選
  • 如果響應時間最重要,並且不能超過1秒,使用併發收集器 CMS 或者G1

設置JVM使用的垃圾回收器
(1)串行
-XX:+UseSerialGC
-XX:+UseSerialOldGC
(2)並行(吞吐量優先):
-XX:+UseParallelGC
-XX:+UseParallelOldGC
(3)併發收集器(響應時間優先)
-XX:+UseConcMarkSweepGC
-XX:+UseG1G

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章