面試官:談談你對JVM垃圾收集器的瞭解

yes的練級手冊發表於2019-04-28

本文預設JVM為HotSpot。之前已經介紹過常見的垃圾收集演算法,這次再來說說JVM具體實現了這些演算法的垃圾收集器。

下圖為HotSpot虛擬機器有的垃圾收集器,有連線關係的表示可以搭配使用,反之不能。

HotSpot虛擬機器垃圾收集器
每種垃圾收集器都有各自的特性,我們需要根據不同的場景來使用不同的垃圾收集器。

新生代垃圾收集器

Serial

此垃圾收集器年代久遠,用於新生代的垃圾收集,採用複製演算法。是單執行緒的垃圾收集器也就是不管你的伺服器有多少CPU,反正它就用其中的一個CPU啟動一個執行緒去處理垃圾回收,並且停止所有工作執行緒等待它回收完成。所以它在收集時會STW(stop the world)。能與其搭配的老年代收集器是CMS與Serial Old。

Serial與Serial Old搭配
單執行緒的好處就在於它簡單,沒有上下文執行緒切換的開銷。多用於桌面應用中,也就是適用於client模式。因為桌面應用一般佔用的記憶體不大,記憶體不大代表需要處理的垃圾不多,所以即使單執行緒也能處理很快,所以感受不到STW。是client模式下預設的新生代垃圾收集器。

ParNew

此垃圾收集器可以說是Serial的多執行緒版本,它和Serial的差別就在於複製的時候是多執行緒的。

ParNew與Serial Old搭配
它主要是能利用多CPU,提升複製的速度,減少STW的時間。但是在單CPU情況下不要使用它,因為執行緒切換有開銷,效能不一定會比Serial好,當然如果CPU數很多的話那效能肯定是比Serial好的。所以在Server模式下可以用它來作為新生代垃圾處理器。能與其搭配的老年代收集器是CMS與Serial Old。

Parallel Scavenge

Scavenge是撿破爛的意思...恩並行撿破爛說的是好像沒錯,用的也是複製演算法。那不是已經在ParNew了嗎,怎麼還來個並行的。它和ParNew主要有兩個不同點

1、Parallel Scavenge的關注點在於可控制的吞吐量,吞吐量=執行使用者程式碼的時間/(執行使用者程式碼的時間+GC時間)。就是說它的重點不在於想縮短每次GC的時間,而在於控制虛擬機器執行一段時間中,所花費在GC上的總時間。比如程式執行了100分鐘,其間垃圾收集了1分鐘,那吞吐量就是99%。

2、Parallel Scavenge能自適應調節新生代中配置的引數,例如Eden和survivor比例等。其實就是因為它能自適應,所以才能可控制吞吐量,它根據實際情況動態調整這些引數來達到要求的吞吐量。

此收集器也提供了“-XX:MaxGCPauseMillis”控制垃圾收集最大停頓時間(允許值大於0),“-XX:GCTimeRatio”吞吐量(1-99)。 看到“-XX:MaxGCPauseMillis”,別以為我們想設定多少就多少,收集器只能儘可能的保證而已。而且說白了能如果想提高新生代GC的速度,那就是減少新生代的記憶體空間,記憶體空間少垃圾肯定少處理起來肯定快。但是空間少是不是更快的容易滿啊,所以所需的GC次數肯定會增多,那吞吐量也會下降。

比如說一個程式現在跑在伺服器上,假設每次新生代GC時間是100毫秒,每10秒鐘一次新生代GC,那一分鐘花費在GC上的時間就是600毫秒。那我想每次花在GC時間更少比如60毫秒,那就減少新生代記憶體空間,但是這樣每5秒鐘一次GC,那一分鐘花費在GC上的時間就是720毫秒。

對應使用的場景就是如果你的服務是計算類的,默默在後臺計算,和使用者互動很少,所以你肯定想的是吞吐量大,也就是總的GC時間短,能充分的用了CPU來計算,這個時候就適合用Parallel Scavenge。

那如果你的程式是互動類的,你的要求肯定就是STW的時間越短越好,能快速響應客戶的請求。Parallel Scavenge也行,但是它不能和CMS聯合使用呀!因為Parallel Scavenge沒有使用原本HotSpot中和其它GC通用的那個GC框架,而是新框架。所以預設和CMS搭配的就是ParNew。

Parallel Scavenge與Parallel Old搭配

老年代垃圾收集器

Serial Old

它是Serial 收集器的老年代版本,是單執行緒收集,採用的是標記-整理演算法。主要用於client模式和CMS的後備收集器。除了G1,上面說的幾個新生代收集器都可以與它搭配使用。圖請參考上面Serial。

Paraller Old

它是Parallel Scavenge的老年代版本,是多執行緒收集,採用的是標記-整理演算法。它只能和Parallel Scavenge搭配。它的出現打破了Parallel Scavenge尷尬的地位,因為之前Parallel Scavenge只能和Serial Old配合,人家新生代都多執行緒跑了,奈何老年代只有單執行緒,拖累它了。圖請參考上面Parallel Scavenge。

CMS

CMS(Concurrent Mark Sweep),從名字可以看出它採用的是標記-清除演算法。它致力於減少STW的時間,讓垃圾收集時同時使用者執行緒也能並行著。在目前的Server主流垃圾收集器。

CMS
它的垃圾收集步驟分為以下4步:

1、初始標記(會STW)

2、併發標記

3、重新標記(會STW)

4、併發清理

初始標記就是僅標記GC Roots直接關聯的物件,不繼續深入標記,致力於減少STW時間。併發標記就是深入標記遍歷後面所有關聯物件。重新標記就是修正因併發標記階段而發生變動了的物件標記會STW。然後就是併發清理垃圾。

所以CMS把所需消耗時間最長的深入標記階段和清理階段與使用者執行緒並行。大大減少了STW所需的時間。

但是它有以下3個缺點:

1、併發階段會與工作執行緒爭搶CPU資源

2、空間碎片問題,因為採取的是標記-清除演算法所以會產生空間碎片。為什麼解決這個問題CMS提供了"-XX:+UseCMSCompactAtFullCollection"(預設開啟),用於當CMS頂不住需要進行FullGC時整理空間碎片,但是整理的過程是使用者執行緒是得停止工作的,所以停頓的時間會變長。

3、浮動垃圾問題。因為在併發清理的時候允許使用者執行緒繼續執行,而執行就可能產生新的垃圾進入老年代,所以需要預留一部分空間給這些浮動垃圾,而當這些浮動垃圾過多在CMS執行期間爆了,那CMS就會出現“Concurrent Mode Failure”,這是時候就得後備的Serial Old上來重新進行老年代的垃圾收集,所以停頓的時間就更長了。

G1

此垃圾收集器不需要和別人配合,自己處理新生代和老年代。在jdk9中G1變為Server模式預設的垃圾收集器。它的發明就是為了替代CMS。

G1(Garbage-First)從整體來看是基於標記-整理的演算法,從區域性來看是基於複製演算法。它和CMS一樣可以和使用者程式並行。相對於CMS 它的優點是首先它能建立可預測的停頓時間模型,能在一個規定的時間段內指定垃圾收集的時間不超過限制的毫秒數,並且它將Java堆分為多個大小相等的獨立區域,也就是Region。雖然它還保留著分代的概念,但是新生代和老年代不是物理隔離了。 它的清理區間不再是整個新生代或者老年代,而是以區域為劃分,不會產生空間碎片

G1會維護一個優先列表,根據跟蹤各個region回收所能產生的空間大小和時間來標定優先順序,優先回收優先順序最大的Region。這就等於每次的回收目標更加精確化,提高回收的效率 G1的收集步驟可分為:

1、初始標記

2、併發標記

3、最終標記

4、篩選回收

G1

初始標記和CMS一樣先標記GC Roots直接關聯物件,然後併發深入標記,遍歷關聯物件。最終標記和CMS重新標記一個概念,篩選回收也就是篩選下決定回收哪個Region價值更大。


如果有錯歡迎指正!個人公眾號:yes的練級攻略

相關文章