前言
本篇講一下各種GC之間的細微差異講的清楚的,希望通過本篇文章,讀者可以對GC的種類有更深刻的了解。
對于GC算法的可參數文章JVM篇之 GC算法
一、串行收集器-Serial
Serial收集器最古老的,最穩定的,歷經考驗,內部的BUG比較少的一個收集器,單個GC線程進行垃圾回收。
會作為CMS收集器降級處理時會使用此收集器。
JVM GC參數
增加JVM參數 -XX:+UseSerialGC 使用串行收集器,啟用此參數后
- 新生代、老年代使用串行回收
- 新生代使用復制算法
- 老年代使用標記-壓縮
回收過程如下圖:
通過上圖,可以看出在應用程序線程到達安全點后,會全部暫停,然后運行GC線程(單線程),GC線程線程執行完成之后,再開始執行應用線程。
缺點:因為是串行的,只使用一個線程進行回收,所以會產生較長的停頓時間,在多核的CPU上,是無法發揮CPU的性能
GC日志
當看GC日志有紅色加粗顯示的標識時,說明當前使用的是Serial收集器GC
二、并行收集器-ParNew
Serial收集器新生代的并行版本,與Serial收集器相比只影響新生代的回收,此收集器需要多核cpu的支持,GC性能會更好。
JVM GC參數
增加JVM參數 -XX:+UseParNewGC ,啟用ParNew收集器,啟用之后
-新生代并行
-新生代使用復制算法
-老年代串行
-老年代使用標記-壓縮算法
由于新生代是并行回收,可以通過參數來調整并行回收的線程數量
-XX:ParallelGCThreads 限制線程數量
ParNew收集器與Serial收集器主要區別在于,新生代的回收,前者是多線程回收,后者是單線程回收
回收過程如下圖:
GC日志
在GC日志中出現上圖中紅色加粗顯示的標識時,說明當前GC使用的是ParNew收集器,如下圖顯示三、Parallel收集器
- Serial收集器在新生代和老年代的多線程版收集器
- ParNew收集器,在老年代的并行版收集器
- 新生代復制算法
- 老年代 標記-壓縮
- 更加關注吞吐量
JVM GC參數
-XX:+UseParallelGC
- 新生代使用Parallel收集器
- 老年代串行
-XX:+UseParallelOldGC
- 新生代使用Parallel收集器
- 并行老年代
回收過程如下圖
GC日志
在GC日志中出現上圖中紅色加粗顯示的標識時,說明當前GC使用的是Parallel收集器,如下圖顯示!其它JVM參數
由于Parallel收集器比較關注吞吐量,所以還提供了一些配套的參數來控制GC的時間和占比
-XX:MaxGCPauseMills(停頓時間)
設置GC的最大停頓時間,單位毫秒
GC盡力保證回收時間不超過設定值
作為一個GC的目標值
-XX:GCTimeRatio(可理解為吞吐量)
GC所使用的CPU時間占用總時間的百分比
在0-100的取值范圍
默認99,即最大允許1%時間做GC
通常情況下,我們希望GC停頓時間短 ,同時又希望吞吐量高,但事實上這兩個參數是矛盾的。因為停頓時間和吞吐量不可能同時調優。在GC工作負載不變的情況下
- 如果提高GC的頻率,因為頻率高了,所以每次GC要處理的垃圾就少了,GC速度自然速度就會加快,但是頻繁的GC會對系統的整體性能有損傷的,就會出現GC停頓時間短了,但是系統整體性能并不會很好。
- 如果降低GC的頻率,自然而然每次要處理的垃圾就會增加,所以會導致每次GC的時間相對就會增加,但是對系統的整體性能是有所上升的。
對吞吐量的理解
- cpu分到應用程序上的時間越多,吞吐量自然就會增加
- cpu分到GC線程上的時間越多,處理應用線程就會越少。
所以吞吐量,可以用應用線程占用CPU時間的長短來衡量。
在停頓時間和吞吐量兩者上,在GC工作負載不變的情況下,不可能兩者同時提高,個人認為除非優化GC算法,從根本上降低GC的工作負載。
所以在調試停頓時間 和吞吐量這兩個參數時,要根據實際情況來調整。
四、CMS收集器
CMS(Concurrent Mark Sweep) 并發標記清除,并發的意思是與用戶線程一起運行,從名字上可以理解,此算法是使用標記-清除算法,比較關注停頓時間 。
JVM GC參數
增加JVM參數 -XX:+UseConcMarkSweepGC,啟用CMS收集器,啟用之后
- 新生代使用ParNew收集器
- 新生代使用復制算法
- 老年代使用CMS收集器
- 老年代使用并發標記-清除
回收過程
因為CMS收集器要與用戶線程一起運行,所以它的算法和實現機制比較復雜,主要工作可以分為五步:
- 初始標記
- 標識根節點直可達的對象
- 速度快
- 會產生STW
- 并發標記(和用戶線程一起)
- 并發標記存活對象
- 占用時間相對較長
- 重新標記
- 由于并發標記時,用戶線程依然運行因以在正式清理前,對有變化的對象,所以需要修正,對對象重新標記
- 會產生STW
- 并發清除(和用戶線程一起)
- 基于標記結果,直接清理不可達的對象
- 并發重置
- 為下一次CMS GC做準備
回收過程如下
GC日志
CMS-initial-mark 初始標記
CMS-concurrent-mark 并發標記
CMS-remark 重新標記
CMS-concurrent-sweep 并發清除
CMS-concurrent-reset 并發重置
具體的日志內容如下:cms收集器的特點
- 盡可能降低停頓
- 會影響系統整體吞吐量和性能
比如,在用戶線程運行過程中,分一半CPU去做GC,系統性能在GC階段,反應速度就下降一半 - 清理不徹底
因為在清理階段,用戶線程還在運行,會產生新的垃圾,無法清理 - 與標記-壓縮相比會產生內存碎片
- 并發階段會降低吞吐量
因為CMS收集器和用戶線程一起運行,所以不能在空間快滿時再清理,因為與用戶線程同時進入,如果在快滿的時候進行清理的話,很容易出兩用戶線程申請內存,出現concurrent mode failure的情況,所以JVM考慮到這點,提供相應的參數進行設置觸發GC的閾值
-XX:CMSInitiatingOccupancyFraction設置觸發GC的閾值
-XX:CMSInitiatingPermOccupancyFraction:當永久區占用率達到這一百分比時,啟動CMS回收
-XX:CMSInitiatingOccupancyFraction:設置CMS收集器在老年代空間被使用多少后觸發
即使設置了閾值也不能保證不會出現并發收集的錯誤 ,如果不幸內存預留空間不夠,就會引起concurrent mode failure,當發生這種情況之后,CMS收集器會降級到Serial收集器進行垃圾回收,這時候會暫停用戶線程,會產生一個長時間的停頓來回收垃圾。
有關碎片
因為CMS采用標記清除算法,所以會產生大量的內存碎片,所以JVM提供了兩個參數來對碎片進行整理
-XX:+ UseCMSCompactAtFullCollection
Full GC后,進行一次整理
整理過程是獨占的,會引起停頓時間變長(因為要移動大量的存活對象)
-XX:+CMSFullGCsBeforeCompaction
設置進行幾次Full GC后,進行一次碎片整理
-XX:ParallelCMSThreads
設定CMS的線程數量
五、各GC收集器對比
收集器 | JVM參數 | 新生代 | 老年代 |
---|---|---|---|
Serial | -XX:+UseSerialGC | 串行、復制算法 | 串行、標記壓縮算法 |
ParNew | -XX:+UseParNewGC | 并行、復制算法 | 串行、標記壓縮算法 |
Parallel | -XX:+UseParallelGC -XX:+UseParallelOldGC | 串行或并行、復制算法 | 串行或者并行、標記壓縮算法 |
CMS | -XX:+UseConcMarkSweepGC | 并行、復制算法 | 并發、標記清除算法 |
六、疑問
- 為什么CMS不直接使用標記-壓縮算法呢?
作者:BK
http://www.lxweimin.com/u/a5230c4f0b7a
鑒于本人才疏學淺,不足之處還望斧正,也歡迎關注我,無特殊說明的都是自己一字一句碼出來的,尊重原創,如果轉載請說明出處!