JVM(五)垃圾回收器的前世此生

全文共 2195 個字,讀完大約須要 8 分鐘。算法

若是垃圾回收的算法屬於內存回收的方法論的話,那本文討論的垃圾回收器就屬於內存回收的具體實現。數組

由於不一樣的廠商(IBM、Oracle),實現的垃圾回收器各不相同,而本文要討論的是 Oracle 的 HotSpot 虛擬機所使用的垃圾回收器。多線程

經常使用垃圾回收器,以下圖所示:併發

經常使用垃圾回收器

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

其中相互連線的垃圾回收器,表示能夠相互搭配使用。性能

新生代 And 老生代

目前經常使用的商用垃圾收集器都使用的是分代垃圾回收方式。學習

分代垃圾回收器把內存分爲:新生代(Young Generation)和老生代(Tenured Generation),以下圖所示:線程

分代圖

(圖片來自fancydeepin)code

默認狀況下,新生代和老生代的內存比例是 1:2,該值能夠經過 -XX:NewRatio 來設定。orm

新生代(Young Generation)

程序中的大部分對象都符合「朝生夕死」的特性,因此絕大數新建立的對象都會存放在新生代,除非是大對象會直接進入老生代。新生代採用的是複製算法,這樣能夠更高效的回收內存空間。對象

新生代有細分爲:Eden、Form Survivor、To Survivor 三個區域,默認的比例是 8:1:1,能夠經過 -XX:SurvivorRatio 來設定。

新生代垃圾回收的執行過程:

一、Eden 區 + From Survivor 區存活着的對象複製到 To Survivor 區;

二、清空 Eden 和 From Survivor 分區;

三、From Survivor 和 To Survivor 分區交換(From 變 To,To 變 From)。

老生代(Tenured Generation)

老生代垃圾回收的頻率比新生代低,存放的主要對象是:

一、新生代對象通過 N 次 GC 晉升到老年代。

能夠經過設置 -XX:MaxTenuringThreshold=5 來設置,默認值是 15 次。

二、大對象直接存儲到老生代。

所謂的「大對象」指的是須要連續存儲空間的對象,好比:數組。

當大對象在新生代存儲不下的時候,就須要分配擔保機制,把當前新生代的全部對象複製到老年代中,由於分配擔保機制須要涉及大量的複製,會致使性能問題,全部最好的方案是直接把大對象存儲到老生代中。

經過參數 -xx:PretrnureSizeThreshold 來設定大對象的值。

注意:該參數只有 Serial 和 ParNew 垃圾回收器有效。

Serial

Serial 最先的垃圾回收器,JDK 1.3.1 以前新生代惟一的垃圾回收器,使用的是單線程串行回收方式,在單 CPU 環境下性能較好,由於單線程執行不存在線程切換。

線程類型: 單線程

使用算法: 複製算法

指定收集器: -XX:+UseSerialGC

Serial Old

Serial 收集器的老年代版本,一樣也是單線程的。它有一個實用的用途做爲CMS收集器的備選預案,後面介紹CMS的時候會詳細介紹。

線程類型: 單線程

使用算法: 標記-整理

指定收集器: -XX:+UseSerialGC

ParNew

ParNew 其實就是 Serial 的多線程版本,能夠和 Serial 共用不少控制參數,好比:-XX:SurvivorRatio , ParNew 能夠和 CMS 配合使用。

parnew

(注:圖片來源於零壹技術棧)

線程類型: 多線程

使用算法: 複製

指定收集器: -XX:+UseParNewGC

Parallel Scavenge

Parallel 和 ParNew 收集器相似,也是多線程的,但 Parallel 是吞吐量優先的收集器,GC停頓時間的縮短是以吞吐量爲代價的,好比收集 100MB 的內存,須要 10S 的時間,CMS 則會縮短爲 7S 收集 50 MB 的內存,這樣停頓的時間確實縮少了,但收集的頻率變大了,吞吐量就變小了。

線程類型: 多線程

使用算法: 複製

指定收集器: -XX:+UseParallelGC

Parallel Old

Parallel Old 是 Parallel 的老生代版本,一樣是吞吐量優先的收集器。

線程類型: 多線程

使用算法: 標記-整理

指定收集器: -XX:+UseParallelOldGC

CMS

CMS(Concurrent Mark Sweep)一種以得到最短停頓時間爲目標的收集器,很是適用B/S系統。

使用 Serial Old 整理內存。

CMS 運行過程:

CMS

(注:圖片來源於零壹技術棧)

一、初始標記

標記 GC Roots 直接關聯的對象,須要 Stop The World 。

二、併發標記

從 GC Roots 開始對堆進行可達性分析,找出活對象。

三、從新標記

從新標記階段爲了修正併發期間因爲用戶進行運做致使的標記變更的那一部分對象的標記記錄。這個階段的停頓時間通常會比初始標記階段稍長一些,但遠比並發標記的時間短,也須要 Stop The World 。

四、併發清除

除垃圾對象。

CMS 缺點:

一、對 CPU 資源要求敏感。

CMS 回收器過度依賴於多線程環境,默認狀況下,開啓的線程數爲(CPU 的數量 + 3)/ 4,當 CPU 數量少於 4 個時,CMS 對用戶自己的操做的影響將會很大,由於要分出一半的運算能力去執行回收器線程。

二、CMS沒法清除浮動垃圾。

浮動垃圾指的是CMS清除垃圾的時候,還有用戶線程產生新的垃圾,這部分未被標記的垃圾叫作「浮動垃圾」,只能在下次 GC 的時候進行清除。

三、CMS 垃圾回收會產生大量空間碎片。

CMS 使用的是標記-清除算法,全部在垃圾回收的時候回產生大量的空間碎片。

注意:CMS 收集器中,當老生代中的內存使用超過必定的比例時,系統將會進行垃圾回收;當剩餘內存不能知足程序運行要求時,系統將會出現 Concurrent Mode Failure,臨時採用 Serial Old 算法進行清除,此時的性能將會下降。

線程類型: 多線程

使用算法: 標記-清除

指定收集器: -XX:+UseConcMarkSweepGC

G1

G1 GC 這是一種兼顧吞吐量和停頓時間的 GC 實現,是 JDK 9 之後的默認 GC 選項。G1 能夠直觀的設定停頓時間的目標,相比於 CMS GC,G1 未必能作到 CMS 在最好狀況下的延時停頓,可是最差狀況要好不少。

G1 GC 仍然存在着年代的概念,可是其內存結構並非簡單的條帶式劃分,而是相似棋盤的一個個 region。Region 之間是複製算法,但總體上實際可看做是標記 - 整理(Mark-Compact)算法,能夠有效地避免內存碎片,尤爲是當 Java 堆很是大的時候,G1 的優點更加明顯。

G1

G1 吞吐量和停頓表現都很是不錯,而且仍然在不斷地完善,與此同時 CMS 已經在 JDK 9 中被標記爲廢棄(deprecated),因此 G1 GC 值得深刻掌握。

G1 運行過程:

一、初始標記

標記 GC Roots 直接關聯的對象,須要 Stop The World 。

二、併發標記

從 GC Roots 開始對堆進行可達性分析,找出活對象。

三、從新標記

從新標記階段爲了修正併發期間因爲用戶進行運做致使的標記變更的那一部分對象的標記記錄。這個階段的停頓時間通常會比初始標記階段稍長一些,但遠比並發標記的時間短,也須要 Stop The World 。

四、篩選回收

首先對各個 Region 的回收價值和成本進行排序,根據用戶所指望的 GC 停頓時間來制定回收計劃。這個階段能夠與用戶程序一塊兒併發執行,可是由於只回收一部分 Region,時間是用戶可控制的。

線程類型: 多線程

使用算法: 複製、標記-整理

指定收集器: -XX:+UseG1GC(JDK 7u4 版本後可用)

參考

《深刻理解Java虛擬機》

《垃圾回收的算法與實現》

最後

關注公衆號,發送「gc」關鍵字,領取《垃圾回收的算法與實現》學習資料。

相關文章
相關標籤/搜索