據說微信搜索《Java魚仔》會變動強哦!git
本文收錄於JavaStarter ,裏面有我完整的Java系列文章,學習或面試均可以看看哦github
(一)概述
若是說垃圾收集算法是內存回收的理論,那麼垃圾收集器就是內存回收的具體實現。面試
垃圾收集器目前存在的有不少,可是依舊沒有哪一個收集器是萬能的存在,咱們只能選擇一個最適合應用的收集器。算法
下面會介紹目前主流Java虛擬機中所採用的七種垃圾收集器: Serial、parNew、ParallelScavenge、SerialOld、ParallelOld、CMS、G1 上述垃圾收集器有些適用於新生代,有些適用於老年代,有些在新生代和老年代都適應。以下圖所示,連線表示能夠配合使用。微信
(二)Serial
Serial是一個單線程的收集器,Serial的特色是它在進行垃圾收集時,必須「Stop the World」,意思就是當這個垃圾收集器開始工做時,必須中止其餘全部的工做線程。聽起來彷佛很不靠譜,可是對於限定單個CPU的場景下,這種方式簡單而高效。對於簡單的桌面應用,分配給虛擬機的內存不會很大,對於一兩百兆的新生代,Serial的垃圾收集時間能夠控制在一百毫秒之內,對於用戶來講基本上是無影響的。多線程
Serial收集器在新生代使用複製算法。併發
(三)ParNew
ParNew垃圾收集器是Serial的多線程版本,使用多條線程進行垃圾收集。除此以外,和Serial基本相同,ParNew在多線程收集垃圾時依舊須要**「Stop the World」**。ParNew可使用-XX:ParallelGCThreads參數來限制垃圾收集的線程數量。學習
ParNew收集器在新生代使用複製算法線程
(四)Parallel Scavenge
Parallel Scavenge也是新生代收集器,也一樣是多線程的收集器,可是和ParNew不一樣,Parallel Scavenge收集器關注的是一個可控制的吞吐量(Throughput)。所謂吞吐量指的是CPU用於運行代碼的時間和CPU總消耗的時間比例。對象
吞吐量=運行代碼的時間 /(運行代碼的時間+垃圾收集時間)
理論上吞吐量越高,用戶就越不能感覺到停頓時間。
Parallel Scavenge提供了兩個參數用來控制吞吐量: -XX:MaxGCPauseMillis和**-XX:GCTimeRatio**
-XX:MaxGCPauseMillis設置內存回收花費時間最高毫秒值,可是不要一味地認爲只要把值設置很小,垃圾回收就更快了。這個停頓時間是以犧牲吞吐量和新生代空間換來的。
-XX:GCTimeRatio表示垃圾收集時間佔總時間的比例,(1~100),也就是吞吐量的倒數。默認這個值是99,就是容許最大百分之1的垃圾手機時間(1/(1+99))。
還有一個參數**-XX:+UseAdaptiveSizePolicy**,打開這個參數後,就不須要本身設置新生代大小、晉升老年代對象年齡等參數,所以Parallel Scavenge收集器也被叫作吞吐量優先垃圾收集器。
Parallel Scavenge採用複製算法。
(五)Serial Old
一聽名字就知道這是Serial收集器的老年代版本,是單線程收集器,採用標記-整理算法,其他的和新Serial基本相同。
(六)Parallel Old
Parallel Scavenge收集器的老年版本,多線程收集器,採用標記-整理算法,也是吞吐量優先。
(七)CMS收集器
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器。CMS是基於標記-清除算法的老年代垃圾回收器,CMS是目前應用最普遍的老年代垃圾回收器,它進行垃圾回收分爲如下四步:
一、初始標記:標記GC Roots能夠直接關聯到的對象,速度很快(stop the world)
二、併發標記:根搜索算法的過程
三、從新標記:爲了修正併發標記期間,因程序運行致使標記產生變更的對象。(stop the world)
四、併發清除:清除垃圾
這個過程當中耗時最長的是併發標記和併發清除的過程,可是並不會stop the world,而初始標識和從新標記的速度都很快,即便stop the world也不會佔用太多時間。
它的優勢就是併發收集、併發清除、低停頓。
可是它有三個顯著的缺點:
一、對CPU資源十分敏感,由於併發標記和併發清除都是和程序同時運行,所以會佔用CPU致使應用程序變慢。
二、沒法處理浮動垃圾,浮動垃圾就是在併發清除過程當中新生成的垃圾,這部分垃圾CMS沒法在本次被清理,可能出現Concurrent Mode Failed報錯,所以須要預留必定的內存空間,沒法等到老年代快被佔滿時再清除。默認狀況下,CMS在老年代使用了68%後就會被激活。能夠設置-XX:CMSInitiatingOccupancyFraction設置這個值。
三、產生空間碎片,因爲採用的是標記-清除算法,那就沒法避免會產生空間碎片的問題,這會給分配大對象帶來困難。
(八)G1
上面的垃圾回收器基本上都是按新生代和老年代去區分,可是G1不同
堆結構
G1的堆結構就是把一整塊內存區域劃分爲多個固定大小的塊,JVM通常把堆劃分爲2000個region,而後每一個region從1M到32M不等。
內存的分配
全部的region會被劃分爲Eden、Survivor、Old和Humongous,其中對Eden、Survivor和Old的理解用其餘垃圾回收器去理解,這裏多了一種類型Humongous,這個類型主要用來存儲比標準塊大百分之50或者更大的對象。
G1中的YGC
第一次YGC時,Eden塊中存活的對象會被轉移到一個或多個survivor塊中,存活時間達到閾值,這些對象就會晉升到老年代。年輕代 GC 經過多線程並行進行。
此時會有一次 stop the world暫停,會計算出 Eden大小和 survivor 大小,用於下次young GC。統計信息會被保存下來,用於輔助計算size。好比暫停時間之類的指標也會歸入考慮。
一旦發生一次新生代回收,整個新生代都會被回收(根據對暫停時間的預測值,新生代的大小可能會動態改變)
G1中的老年代垃圾收集
老年代回收不會回收所有老年代空間,只會選擇一部分收益最高的 Region,回收時通常會搭便車——把待回收的老年代 Region 和全部的新生代 Region 放在一塊兒進行回收,這個過程通常被稱爲 Mixed GC
G1中的老年代垃圾收集和CMS收集器很類似
一、初始標記:附加在正常的YGC過程當中,標記全部的根。(stop the world)
二、掃描根區域:掃描Survivor Regions中指向老年代的被初始標記標記的引用及引用的對象,這個階段是併發執行的,可是在年輕代GC發生以前必須完成。(stop the world)
三、併發標記:在整個堆中查找活着的元素,此階段可被YGC打斷
四、再次標記:相似CMS的從新標記,處理併發標記階段產生的新的對象引用,這階段使用了SATB(snapshot-at-the-beginning)算法,該算法比CMS中所採用的快不少。(stop the world)
五、清理階段:G1 GC 會識別徹底空閒的區域和可供進行混合垃圾回收的區域進行清理。(stop the world)
你能夠發現,有四個階段都須要stop the world,爲了下降stop the world的時間,G1使用了RSet(Remembered Set)來記錄不一樣代之間的引用關係。
RSet
RSet記錄了「誰引用了我」,RSet記錄瞭如下兩種引用:
一、老年代 Region 間的引用
二、老年代 Region 到新生代 Region 的引用,Young GC 時直接將這種引用加入 GC Roots。
RSet的工做原理是這樣的,進行 Young GC 時,選擇新生代所在的 Region 做爲 GC Roots,這些 Region 中的 RSet 記錄了老年代->新生代的的跨代引用(「誰引用了我」),從而能夠避免了掃描整個老年代。進行 Mixed GC 時,「老年代->老年代」之間的引用,能夠經過待回收 Region 中的 RSet 記錄得到,「新生代->老年代」之間的引用經過掃描所有的新生代得到(前面提到過 Mixed GC 會搭 Young GC 的便車),也不須要掃描所有老年代。總之,引入 RSet 後,GC 的堆掃描範圍大大減小了。
(九)總結
上面這七種垃圾收集器中最優秀的非G1莫屬,可是它還不夠好,第一個緣由是RSet會佔用必定的內存,第二個緣由是stop the world時間太長了。目前有兩款更新的垃圾回收器:ZGC/C4 垃圾回收器、Shenandoah 垃圾回收器,有興趣的小夥伴能夠去了解下。