JVM之GC算法的實現(垃圾回收器)

上一節:《JVM之GC算法》 知道GC算法的理論基礎,咱們來看看具體的實現。只有落地的理論,纔是真理。html

1、JVM垃圾回收器的結構

JVM虛擬機規範對垃圾收集器應該如何實現沒有規定,由於沒有最好的垃圾收集器,只有最適合的場景。算法

 

 圖中展現了7種做用於不一樣分代的收集器,若是兩個收集器之間存在連線,則說明它們能夠搭配使用。虛擬機所處的區域則表示它是屬於新生代仍是老年代收集器。多線程

7種:serial收集器、parnew收集器、parallel scavenge收集器、serial  old 收集器、parallel old收集器、cms收集器、g1收集器(整堆收集器)、閉包

串行收集:單垃圾收集線程,進行收集工做,用戶進程須要等待併發

並行收集:工做原理與串行同樣,只是在收集垃圾時是多條線程同時進行,收集的效率在通常狀況下天然高於單線程。性能

併發收集:指用戶線程與垃圾收集線程同時工做(併發:同一時間間隔)。用戶程序在繼續運行,而垃圾收集程序運行在另外一個CPU上。spa

吞吐量:吞吐量就是CPU中用於運行用戶代碼的時間與CPU總消耗時間的比值(吞吐量 = 運行用戶代碼時間 / (運行用戶代碼時間 + 垃圾收集時間))線程

一、Serial收集器
Serial(串行)收集器:最基本,最古老的收集器,只有一個線程進行垃圾收集器的工做,而且在進行垃圾收集工做時須要暫停其餘工做線程(stop the word),直到他工做結束;
Serial收集器簡單高效,工做時沒有線程交互的開銷,因此能夠得到很高的單線程收集效率,對於運行在Client模式下的虛擬機來講很適合。htm

"-XX:+UseSerialGC":添加該參數來顯式的使用Serial垃圾收集器。對象

二、Serial Old收集器
  Serial Old收集器是Seria收集器的老年代版本,他一樣是一個單線程收集器,使用" 標記-整理" 算法。
  Serial Old收集器主要用於Client模式下的虛擬機使用。
  Server模式下的兩大用途:

    • 在JDK1.5及以前的版本與Parallel Scavenge收集器搭配使用;
    • 做爲CMS收集器的後備方案,在併發收集發生Conturrent Mode Failure時使用。

三、ParNew 收集器
ParNew(並行)收集器就是Serial收集器的多線程版本,除了在收集垃圾時是啓用多線程並行執行,其餘行爲(控制參數、收集算法、回收策略/Stop The Word、對象分配規則)徹底同樣

 

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

"-XX:+UseConcMarkSweepGC":指定使用CMS後,會默認使用ParNew做爲新生代收集器。
"-XX:+UseParNewGC":強制指定使用ParNew。
"-XX:ParallelGCThreads":指定垃圾收集的線程數量,ParNew默認開啓的收集線程與CPU的數量相同。

四、Parallel Scavenge收集器
Parallel Scavenge收集器 相似於 ParNew 收集器, Parallel Scavenge收集器 更加關注吞吐量(高效的CPU利用率)。CMS等垃圾收集器關注更多的是用戶線程的停頓時間(提搞用戶體驗);Parallel Scavenge 收集器提供不少參數供咱們找到最合適的停頓時間或者最大吞吐量。JDK1.8 默認的方式;

Parallel Scavenge收集器提供了兩個參數來用於精確控制吞吐量,一是控制最大垃圾收集停頓時間的 -XX:MaxGCPauseMillis參數,二是控制吞吐量大小的 -XX:GCTimeRatio參數;

  • 「 -XX:MaxGCPauseMillis」 參數容許的值是一個大於0的毫秒數,收集器將盡量的保證內存垃圾回收花費的時間不超過設定的值(可是,並非越小越好,GC停頓時間縮短是以犧牲吞吐量和新生代空間來換取的,若是設置的值過小,將會致使頻繁GC,這樣雖然GC停頓時間下來了,可是吞吐量也下來了)。
  • 「 -XX:GCTimeRatio」參數的值是一個大於0且小於100的整數,也就是垃圾收集時間佔總時間的比率,默認值是99,就是容許最大1%(即1/(1+99))的垃圾收集時間。
  • 「-XX:UseAdaptiveSizePolicy」參數是一個開發,若是這個參數打開以後,虛擬機會根據當前系統運行狀況收集監控信息,動態調整新生代的比例、老年大大小等細節參數,以提供最合適的停頓時間或最大的吞吐量,這種調節方式稱爲GC自適應的調節策略。

應用場景:注重高吞吐量以及CPU資源敏感的場合,均可以優先考慮Parallel Scavenge+Parallel Old 收集器。

五、Paraller Old收集器
  Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多線程和「標記-整理」算法。
  在JDK1.6中才出現。

六、CMS(Conturrent Mark Sweep)收集器

CMS收集器是一種以獲取最短回收停頓時間爲目標的收集器。CMS收集器是基於「標記-清除」算法實現,它的整個運行過程能夠分爲:

  • 初始標記:標記一下GC Roots能直接關聯到的對象,這個過程速度很快,可是會暫停其餘用戶線程(Stop the word)
  • 併發標記:進行GCRoots Tracing的過程,同時開啓GC和用戶線程,用一個閉包的結構去記錄可達對象,可是在這個階段結束,該閉包不能保證其包含當前全部的可達對象。由於用戶進程可能會不斷的更新引用域,因此GC線程沒法保證可達性分析的實時性。因此這個算法會跟蹤記錄這些發生引用更新的地方。
  • 從新標記:修正併發標記期間因用戶線程繼續運做而致使標記產生變更的那一部分對象的標記記錄,該階段會GC停頓,停頓時間比初始標記時間稍長,單遠比並發標記時間短。
  • 併發清除:開啓用戶線程,同事GC線程清除死亡的對象

CMS收集器運行的整個過程當中,最耗費時間的是併發標記和併發清除,GC收集器線程和用戶線程是一塊兒工做的,因此整體來講,CMS收集器的內存回收過程是與用戶線程一塊兒併發執行的。

優勢:併發收集、低停頓。
缺點:

  • 一、CMS收集器對CPU資源很是敏感。雖然在兩個併發階段不會致使用戶線程停頓,可是會由於佔用了一部分線程而致使應用程序變慢,總吞吐量降低。CMS默認啓動的回收線程數是(CPU數量+3)/4。
  • 二、:CMS收集器沒法處理浮動垃圾,可能出現「Conturrent Mode Failure」失敗而致使另外一次Full GC產生。因爲CMS併發清除階段用戶線程還在運行,伴隨着程序還在產生新的垃圾,這一部分垃圾出如今標記以後,CMS沒法在當次收集中處理掉它們,只能留到下次再清理,這一部分垃圾稱爲「浮動垃圾」。也正是因爲在垃圾收集階段用戶線程還在運行,那麼也就須要預留有足夠的內存空間給用戶線程使用,所以CMS收集器不能像其餘收集器那樣等待老年代填滿以後再進行收集,須要預留一部分空間給併發收集時用戶程序使用。能夠經過「-XX:CMSInitiatingOccupancyFraction」參數設置老年代內存使用達到多少時啓動收集。
  • 三、:因爲CMS收集器是一個基於「標記-清除」算法的收集器,那麼意味着收集結束會產生大量碎片,有時候每每還有不少內存未使用,可是沒有一塊連續的空間來分配這個大對象,致使不得不提早觸發一次Full GC。CMS收集器提供了一個「-XX:UseCMSCompactAtFullCollection」參數(默認是開啓的)用於在CMS收集器頂不住要FullGC時開啓內存碎片整理(內存碎片整理意味着沒法併發執行不得不停頓用戶線程)。參數「-XX:CMSFullGCsBeforeCompaction」來設置執行多少次不壓縮的Full GC後,跟着來一次帶壓縮的(默認值是0,意味着每次進入Full GC時都進行碎片整理)。

七、G1(Garbage-First)收集器

G1的內存模型

G1收集器沒有新生代和老年代的概念,而是將Java堆劃分爲一塊塊獨立的大小相等的Region。當要進行垃圾收集時,首先估計每一個Region中的垃圾數量,每次都從垃圾回收價值最大的Region開始回收,所以能夠得到最大的回收效率

Humongous是特殊的Old類型,專門放置大型對象.這樣的劃分方式意味着不須要一個連續的內存空間管理對象.G1將空間分爲多個區域,優先回收垃圾最多的區域.
G1採用的是Mark-Copy ,有很是好的空間整合能力,不會產生大量的空間碎片
G1的一大優點在於可預測的停頓時間,可以儘量快地在指定時間內完成垃圾回收任務,在JDK11中,已經將G1設爲默認垃圾回收器,經過jstat命令能夠查看垃圾回收狀況,在YGC時S0/S1並不會交換.

一個對象和它內部所引用的對象可能不在同一個Region中,那麼當垃圾回收時,是否須要掃描整個堆內存才能完整地進行一次可達性分析?
固然不是,每一個Region都有一個Remembered Set,用於記錄本區域中全部對象引用的對象所在的區域,從而在進行可達性分析時,只要在GC Roots中再加上Remembered Set便可防止對全部堆內存的遍歷.

回收步驟

初始標記:標記與GC Roots直接關聯的對象,中止全部用戶線程,只啓動一條初始標記線程,這個過程很快.
併發標記:進行全面的可達性分析,開啓一條併發標記線程與用戶線程並行執行.這個過程比較長.
最終標記:標記出併發標記過程當中用戶線程新產生的垃圾.中止全部用戶線程,並使用多條最終標記線程並行執行.
篩選回收:回收廢棄的對象.此時也須要中止一切用戶線程,並使用多條篩選回收線程並行執行.

G1爲何能創建可預測的停頓時間模型?

由於它有計劃的避免在整個Java堆中進行全區域的垃圾收集。G1跟蹤各個Region裏面的垃圾堆積的大小,在後臺維護一個優先列表,每次根據容許的收集時間,優先回收價值最大的Region。這樣就保證了在有限的時間內能夠獲取儘量高的收集效率。

G1與其餘收集器的區別?

其餘收集器的工做範圍是整個新生代或者老年代、G1收集器的工做範圍是整個Java堆。在使用G1收集器時,它將整個Java堆劃分爲多個大小相等的獨立區域(Region)。雖然也保留了新生代、老年代的概念,但新生代和老年代再也不是相互隔離的,他們都是一部分Region(不須要連續)的集合。

2、如何選擇垃圾收集器

一、單CPU或者小內存,單機程序 — -XX:+UseSerialGC
二、多CPU,須要大吞吐量,如後臺計算型應用,容許工做線程停頓超過1秒  -XX:+UseParallelGC + -XX:+UseParallelOldGC
三、多CPU,追求低停頓時間,快速響應如互聯網應用  -XX:+UseParNewGC + -XX:+UseConcMarkSweepGC

四、JVM本身選擇

五、官方推薦G1,高性能

相關文章
相關標籤/搜索