上一篇部落格我們介紹了Java虛擬機器垃圾回收,介紹了幾種常用的垃圾回收演算法,包括標記-清除,標記整理,複製等,這些演算法我們可以看做是記憶體回收的理論方法,那麼在Java虛擬機器中,由誰來具體實現這些方法呢?
沒錯,就是本篇部落格介紹的內容——垃圾收集器。
1、垃圾收集器種類
事實上Java虛擬機器規範對垃圾收集器應該如何實現,並沒有任何的規定,所以不同的廠商、不同版本的虛擬機器所提供的垃圾收集器都會有所不同,並且一般都會提供引數供使用者根據自己的應用特點和要求組合出各個年代所使用的收集器。
下圖是基於 Sun HotSpot 虛擬機器1.6版 Update 22的虛擬機器種類:
由上圖我們可以總結出幾個結論:
①、新生代垃圾收集器:Serial、ParNew、Parallel Scavenge;
老年代垃圾收集器:Serial Old(MSC)、Parallel Old、CMS;
整堆垃圾收集器:G1
②、垃圾收集器之間的連線表示可以搭配使用,有如下幾種組合:
Serial/Serial Old、Serial/CMS、ParNew/Serial Old、ParNew/CMS、Parallel Scavenge/Serial Old、Parallel Scavenge/Parallel Old、G1;
③、序列收集器Serial:Serial、Serial Old
並行收集器 Parallel:Parallel Scavenge、Parallel Old
併發收集器:CMS、G1
ps:對於文章中有一些名詞不理解的,可以先看本篇部落格最後一個小節。
2、Serial收集器
這是一個最基本,歷史最悠久的垃圾收集器,是JDK1.3之前新生代唯一的垃圾收集器。
該收集器有如下特點:
①、作用於新生代
由上圖也可看出,這是一個新生代垃圾收集器,採用的垃圾回收演算法是複製演算法。
②、單執行緒
工作時只會使用一個CPU或者一條收集執行緒去完成工作。
③、進行垃圾收集時,必須暫停所有工作執行緒
也就是說使用Serial收集器進行垃圾回收時,別的工作執行緒都暫停,系統這時候會有卡頓現象產生。
④、適用場景
Serial 收集器由於沒有執行緒互動的開銷,對於限定單個CPU的環境,可以獲得最高的單執行緒收集效率。
一般在使用者的桌面場景中,分配給虛擬機器管理的記憶體一般來說不會很大,收集幾十兆或一兩百兆的新生代,定頓時間可以控制在幾十毫秒,只要不是頻繁發生的,這點停頓是可以接受的。
所以 Serial 收集器對於執行在 Client 模式下的虛擬機器是一種很好的選擇。
3、ParNew收集器
這個收集器其實就是Serial收集器的多執行緒版本。
也就是說其特點除了多執行緒,其餘和Serial收集器一樣,事實上,這兩個收集器實現上也共用了很多程式碼。
①、作用於新生代
一個新生代垃圾收集器,採用的垃圾回收演算法是複製演算法。
②、多執行緒
彌補了Serial收集器單執行緒的缺陷。
③、適用場景
由於其多執行緒的特性,是大多數執行在 Server 模式下的虛擬機器首選新生代垃圾收集器。
另外需要說明的是,能夠與下面將要介紹的劃時代垃圾收集器CMS(Concurrent Mark Sweep)配合使用,也是一個重要原因。
4、Parallel Scavenge收集器
前面介紹的垃圾收集器關注點是儘可能縮小垃圾收集時的使用者執行緒停頓時間。而 Parallel Scanvenge 收集器是為了達到一個可控制的吞吐量。
吞吐量 = 執行使用者程式碼的時間 / (執行使用者程式碼的時間+垃圾收集時間)
可以用下面兩個引數進行精確控制:
-XX:MaxGCPauseMills 設定最大垃圾收集停頓時間
-XX:GCTimeRatio 設定吞吐量大小
①、作用於新生代
一個新生代垃圾收集器,採用的垃圾回收演算法是複製演算法。
②、多執行緒
並行的多執行緒垃圾收集器。
③、吞吐量
這個收集器可以精確控制吞吐量。
④、適用場景
設定垃圾收集停頓時間短適合需要與使用者快速互動的程式;
而設定高吞吐量可以最高效的利用CPU效率,儘快的完成程式的運算任務,主要適合在後臺運算而不需要太多互動的任務。
5、Serial Old收集器
Serial Old 收集器是 Serial 收集器的老年代版本,特點如下:
①、作用於老年代
②、單執行緒
③、使用標記-整理演算法
④、進行垃圾收集時,必須暫停所有工作執行緒
6、Parallel Old收集器
Parallel Old 是 Parallel Scavenge收集器的老年代版本,使用多執行緒和“標記-整理”演算法。
①、作用於老年代
②、多執行緒
③、使用標記-整理演算法
除了具有以上幾個特點,比較關鍵的是能和新生代收集器 Parallel Scavenge 配置使用,獲得吞吐量最大化的效果。
7、CMS收集器
CMS,全稱為 Concurrent Mark Sweep ,顧名思義併發的,採用標記-清除演算法。另外也將這個收集器稱為併發低延遲收集器(Concurrent Low Pause Collector)
這是一款跨時代的垃圾收集器,真正做到了垃圾收集執行緒與使用者執行緒(基本上)同時工作。和 Serial 收集器的 Stop The World(媽媽打掃房間的時候,你不能再將垃圾丟到地上) 相比,真正做到了媽媽一邊打掃房間,你一邊丟垃圾。
①、作用於老年代
②、多執行緒
③、使用標記-清除演算法
整個演算法過程分為如下 4 步:
一、初始標記(CMS initial mark):只是僅僅標記GC Root 能夠直接關聯的物件,速度很快,但是需要“Stop The World”
二、併發標記(CMS concurrent mark):進行GC Root Tracing的過程,簡單來說就是遍歷Initial Marking階段標記出來的存活物件,然後繼續遞迴標記這些物件可達的物件。
三、重新標記(CMS Remark):修正併發標記期間,因使用者程式繼續執行而導致標記產生變動的那一部分物件的標記記錄,需要“Stop The World”。這個時間一般比初始標記長,但是遠比並發標記時間短。
四、併發清除(CMS concurrent sweep):對上一步標記的物件進行清除操作。
由於整個過程最耗時的操作是第二(併發標記)、四步(併發清除),而這兩步垃圾收集器執行緒是可以和使用者執行緒一起工作的。所以整體來說,CMS垃圾收集和使用者執行緒是一起併發的執行的。
缺點:
①、對CPU資源敏感
因為在併發階段,會佔用一部分CPU資源,從而導致應用程式變慢,總吞吐量會降低。
②、產生浮動垃圾
由於CMS併發清理階段使用者執行緒還在工作,這個時候產生的垃圾,CMS無法在本次收集中處理掉它們,只能留在下一次GC時再將其處理掉,這部分垃圾稱為“浮動垃圾”。
③、產生記憶體垃圾碎片
因為採用的演算法是標記-清除,很明顯,會有空間碎片產生。
8、G1收集器
這是當前收集器技術發展的最前沿的成果。可以實現在基本不犧牲吞吐量的前提下完成低停頓的記憶體回收。
這是因為它並不像前面介紹的所有垃圾收集器是區分新生代,老年代的,它作用於全區域。將整個Java堆劃分為多個大小固定的獨立區域(Regin),並且跟蹤這些區域的垃圾堆積面積,在後臺維護一個優先順序列表,每次根據允許的收集時間,優先回收垃圾最多的區域,這樣保證了G1收集器在有限的時間內可以獲得最高的收集效率。
它與前面講的 CMS 垃圾收集器相比,有兩個顯著的改進:
①、採用 標記-整理 的回收演算法
這樣不會產生空間碎片
②、可以精確的控制停頓時間
能讓使用者明確指定一個長度為M毫秒的時間片內,消耗在垃圾回收上的時間不超過 N 毫秒。
③、作用於整個Java堆
G1收集器不區分年輕代和老年代,是整堆垃圾收集器。
9、如何選擇垃圾收集器
詳細文件可以檢視官方介紹,如下
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/collectors.html
這裡我們翻譯一下結論:
除非應用程式有相當嚴格的暫停時間要求,否則就讓JVM自己選擇垃圾收集器。並且可以適當優先調整堆的大小來提高效能。如果還不滿足要求,則以下面四點作為指導:
1. 如果應用程式記憶體小於100M,那麼使用選項選擇序列收集器-XX:+UseSerialGC
。
2. 如果應用程式將在單核處理器上執行,並且沒有停頓時間的要求,選擇序列-XX:+UseSerialGC
或者 JVM 自己選
3. 如果允許停頓時間超過1秒,選擇並行或 JVM 自己選
4. 如果響應時間比總吞吐量更重要,並且垃圾收集暫停必須保持短於大約1秒,則使用-XX:+UseConcMarkSweepGC
或選擇併發收集器-XX:+UseG1GC
。
10、幾個名詞解釋
①、並行
指多條垃圾收集執行緒並行工作,但此時使用者執行緒仍然處於等待狀態。
適合科學計算、後臺處理等弱互動場景。
②、併發
指使用者執行緒與垃圾收集器執行緒同時執行(但不一定是並行的,可能會交替執行),使用者執行緒繼續執行,而垃圾收集執行緒執行在另一塊CPU上。
適合對響應快速的場景,比如Web。
③、停頓時間
垃圾收集器做垃圾回收中斷應用執行的時間。
④、吞吐量
吞吐量 = 執行使用者程式碼的時間 / (執行使用者程式碼的時間+垃圾收集時間)