GC邏輯分類
垃圾收集器沒有在規范中進行過多的規定,可以由不同的廠商、不同版本的JVM來實現。
由於JDK的版本處於高速迭代過程中,因此Java發展至今已經衍生了眾多的GC版本。
從不同角度分析垃圾收集器,可以將GC分為不同的類型。
按線程數分(垃圾回收線程數)
可以分為串行垃圾回收器和並行垃圾回收器
串行回收
- 串行回收指的是在同一時間段內只允許有一個CPU用於執行垃圾回收操作,此時工作線程被暫停,直至垃圾收集工作結束。
- ➢在諸如單CPU處理器或者較小的應用內存等硬件平台不是特別優越的場 合,串行回收器的性能表現可以超過並行回收器和並發回收器。所以,串行回收默認被應用在客戶端的Client模式下的JVM中
- ➢在並發能力比較強的CPU上,並行回收器產生的停頓時間要短於串行回收器。
並行回收
- 和串行回收相反,並行收集可以運用多個CPU同時執行垃圾回收。因此提升 了應用的吞吐量,不過並行回收仍然與串行回收一樣,采用獨占式,使用了“ Stop一the一world”機制。
按照工作模式分,可以分為並發式垃圾回收器和獨占式垃圾回收器
- 獨占式垃圾回收器(Stop the world)一旦運行,就停止應用程序中的所有用戶線程,直到垃圾回收過程完全結束。
- 並發式垃圾回收器與應用程序線程交替工作,以盡可能減少應用程序的停頓時間。
按碎片處理方式分,可分為壓縮式垃圾回收器和非壓縮式垃圾回收器
- 壓縮式(Compat)垃圾回收器會在回收完成后,對存活對象進行壓縮整理,消除回收后的碎片。
- 再分配對象空間使用: 指針碰撞。
- 非壓縮式的垃圾回收器不進行這步操作。
- 再分配對象空間使用: 空閑列表。
按工作的內存區間分
又可分為年輕代垃圾回收器和老年代垃圾回收器
評估GC的性能指標
-
吞吐量:運行用戶代碼的時間占總運行時間的比例
- (總運行時間:程序的運行時間十內存回收的時間)
-
垃圾收集開銷:吞吐量的補數,垃圾收集所用時間與總運行時間的比例。
-
暫停時間:執行垃圾收集時,程序的工作線程被暫停的時間
-
收集頻率:相對於應用程序的執行,收集操作發生的頻率。
-
內存占用: Java堆區所占的內存大小
-
快速:一個對象從誕生到被回收所經歷的時間。
-
這三者共同構成一個“不可能三角”。三者總體的表現會隨着技術進步而越來越好。一款優秀的收集器通常最多同時滿足其中的兩項。
-
這三項里,暫停時間的重要性日益凸顯。因為隨着硬件發展,內存占用 多些越來越能容忍,硬件性能的提升也有助於降低收集器運行時對應用程序的影響,即提高了吞吐量。而內存的擴大,對延遲反而帶來負面效果。
-
簡單來說,主要抓住兩點:
- 吞吐量
- 暫停時間
吞吐量
- 吞吐量就是CPU用於運行用戶代碼的時間與CPU總消耗時間的比值,即吞吐量=運行用戶代碼時間/ (運行用戶代碼時間+垃圾收集時間)
- ➢比如:虛擬機總共運行了100分鍾,其中垃圾收集花掉1分鍾,那吞吐量就是99%
- 這種情況下,應用程序能容忍較高的暫停時間,因此,高吞吐量的應用程序有更長的時間基准,快速響應是不必考慮的。
- 吞吐量優先,意味着在單位時間內,STW的時間最短: 0.2 + 0.2 = 0.4

暫停時間
- “暫停時間”是指一個時間段內應用程序線程暫停,讓GC線程執行的狀態
- ➢例如,GC期間100毫秒的暫停時間意味着在這100毫秒期間內沒有應用程序線程是活動的。.
- 暫停時間優先,意味着盡可能讓單次STW的時間最短: 0.1 + 0.1 + 0.1 + 0.1+0.1=0.5
高吞吐與低暫定對比
- 高吞吐量較好因為這會讓應用程序的最終用戶感覺只有應用程序線程在做“生產性”工作。直覺上,吞吐量越高程序運行越快。
- 低暫停時間(低延遲)較好因為從最終用戶的角度來看不管是GC還是其他原因導致一個應用被掛起始終是不好的。這取決於應用程序的類型,有時候甚至短暫的200毫秒暫停都可能打斷終端用戶體驗。因此,具有低的較大暫停時間是非常重要的,特別是對於一一個交互式應用程序。
- 不幸的是”高吞吐量”和”低暫停時間”是一對相互競爭的目標(矛盾)。
- ➢因為如果選擇以吞吐量優先,那么必然需要降低內存回收的執行頻率,但是這樣會導致GC需要更長的暫停時間來執行內存回收。
- ➢相反的,如果選擇以低延遲優先為原則,那么為了降低每次執行內存回收時的暫停時間,也只能頻繁地執行內存回收,但這又導致程序吞吐量的下降。
- 在設計(或使用) GC算法時,我們必須確定我們的目標: 一個GC算法只可能針對兩個目標之一(即只專注於較大吞吐量或最小暫停時間),或.嘗試找到一個二者的折衷。
- 現在標准:在最大吞吐量優先的情況下,降低停頓時間。
經典垃圾回收器發展
有了虛擬機,就一定需要收集垃圾的機制,這就是Garbage Collection, 對應的產品我們稱為Garbage Collector.
- 1999年隨JDK1.3.1一 起來的是串行方式的Serial GC,它是第一款GC。ParNew垃圾收集器是Serial收集器的多線程版本
- 2002年2月26日,Parallel GC和Concurrent Mark Sweep GC跟隨JDK1.4.2一起發布
- Parallel GC在JDK6之后成為HotSpot默認GC。
- 2012年,在JDK1.7u4版本中,G1可用。
- 2017年,JDK9中G1變成默認的垃圾收集器,以替代CMS。
- 2018年3月,JDK10中G1垃圾回收器的並行完整垃圾回收,實現並行性來改善最壞情況下的延遲。
- ==------------分水嶺------------==
- 2018年9月,JDK11發布。引入Epsilon垃圾回收器,又被稱為"No一0p (無操作) "回收器。同時,引入ZGC:可伸縮的低延遲垃圾回收器(Experimental)。
- 2019年3月,JDK12發布。 增強G1,自動返回未用堆內存給操作系統。同時,引入Shenandoah GC:低停頓時間的GC (Experimental)。
- 2019年9月,JDK13發布。增強ZGC,自動返回未用堆內存給操作系統。
- 2020年3月,JDK14發布。刪除CMS垃圾回收器。擴展ZGC在macOS和Windows.上的應用
7款經典的垃圾收集器
- 串行回收器:Serial. Serial Old
- 並行回收器:ParNew. Parallel Scavenge. Parallel Old
- 並發回收器:CMS. G1(分區算法)

7款經典的垃圾收集器與垃圾分代之間的關系
- 新生代收集器: Serial、 ParNeW、Parallel Scavenge;
- 老年代收集器: Serial Old、 Parallel Old、 CMS;
- 整堆收集器: G1;
垃圾收集器的組合關系
- 兩個收集器間有連線,表明它們可以搭配使用: Serial/Serial Old、Serial/CMS、 ParNew/Serial Old、ParNew/CMS、 Parallel Scavenge/Serial Old、Parallel Scavenge/Parallel Old、G1;
- 其中Serial Old作為CMS 出現"Concurrent Mode Failure"失敗的后備預案。
- (紅色虛線)由於維護和兼容性測試的成本,在JDK 8時將Serial+CMS、 ParNew+Serial Old這兩個組合聲明為廢棄(JEP 173) ,並在JDK 9中完全取消了這些組合的支持(JEP214),即:移除。
- (綠色虛線)JDK 14中:棄用Parallel Scavenge和SerialOld GC組合(JEP366 )
- (青色虛線)JDK 14中:刪除CMS垃圾回收器 (JEP 363)
為什么要有很多收集器個不夠嗎? 因為Java的使用場景很多, 移動端,服務器等。所以就需要針對不同的場景,提供不同的垃圾收集器,提高垃圾收集的性能。
雖然我們會對各個收集器進行比較,但並非為了挑選一個最好的收集器出來。沒有一種放之四海皆准、任何場景下都適用的完美收集器存在,更加沒有萬能的收集器。所以我們選擇的只是對具體應用最合適的收集器。
查看默認的垃圾收集器
方法1:-xx:+PrintCommandLineFlags: 查看命令行相關參數(包含使用的垃圾收集器)
方法2:使用命令行指令: jinfo 一flag相關垃圾回收器參數進程ID
示例代碼:/** * -XX:+PrintCommandLineFlags * * -XX:+UseSerialGC:表明新生代使用Serial GC ,同時老年代使用Serial Old GC * * -XX:+UseParNewGC:標明新生代使用ParNew GC * * -XX:+UseParallelGC:表明新生代使用Parallel GC * -XX:+UseParallelOldGC : 表明老年代使用 Parallel Old GC * 說明:二者可以相互激活 * * -XX:+UseConcMarkSweepGC:表明老年代使用CMS GC。同時,年輕代會觸發對ParNew 的使用 */ public class GCUseTest { public static void main(String[] args) { ArrayList<byte[]> list = new ArrayList<>(); while(true){ byte[] arr = new byte[100]; list.add(arr); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } }
打印輸出:
-XX:InitialHeapSize=268435456 -XX:MaxHeapSize=4294967296 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
命令行方式:
jdk8 使用的是parallel
Serial回收器:串行回收
- Serial收集器是最基本、歷史最悠久的垃圾收集器了。JDK1.3之前回收新生代唯一的選擇。
- Serial收集器作為HotSpot中Client模式下的默認新生代垃圾收集器。
- Serial收集器采用復制算法、串行回收和"Stop一 the一World"機制的方式執行內存回收。 )
- 這個收集器是一個單線程的收集器,但它的“單線程”的意義並不僅僅說明它只會使用一個CPU或一條收集線程去完成垃圾收集工作,更重要的是在它進行垃圾收集時,必須暫停其他所有的工作線程,直到它收集結束(Stop The World )。
Serial Old收集器
除了年輕代之外,Serial收集器還提供用於執行老年代垃圾收集的Serial Old收集器。 Serial Old收集器同樣也采用了串行回收 和"Stop the World"機制。
- 只不過內存回收算法使用的是標記一壓縮算法。
- ➢Serial Old是運行在Client模式下默認的老年代的垃圾回收器
- ➢Serial 0Od在Server模式下主要有兩個用途:①與新生代的ParallelScavenge配合使用; ②作為老年代CMS收集器的后備垃圾收集方案。
Serial回收器的優勢
- 簡單而高效(與其他收集器的單線程比),對於限定單個CPU的環境來說,Seria1收集器由於沒有線程交互的開銷,專心做垃圾收集自然可以獲得最高的單線程收集效率。
- ➢運行在Client模式下的虛擬機是個不錯的選擇。
- 在用戶的桌面應用場景中,可用內存一般不大(幾十MB至一兩百MB), 可以在較短時間內完成垃圾收集(幾十ms至一百多ms) ,只要不頻繁發生,使用串行回收器是可以接受的。
- 在HotSpot虛擬機中,使用-XX: +UseSerialGC 參數可以指定年輕代和老年代都使用串行收集器。
- 等價於新生代用Serial GC,且老年代用Serial Old GC
- 控制台輸出
-XX:InitialHeapSize=268435456 -XX:MaxHeapSize=4294967296 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseSerialGC
Serial回收器總結
- 這種垃圾收集器大家了解,現在已經不用串行的了。而且在限定單核cpu才可以用。現在都不是單核的了。
- 對於交互較強的應用而言,這種垃圾收集器是不能接受的。一般在Javaweb應用程序中是不會采用串行垃圾收集器的。
ParNew回收器:並行回收
-
如果說Serial GC是年輕代中的單線程垃圾收集器,那么ParNew收集器則是Serial收集器的多線程版本。
- ➢Par是Parallel的縮寫,New: 只能處理的是新生代
-
ParNew收集器除了采用並行回收的方式執行內存回收外,兩款垃圾收集器之間幾乎沒有任何區別。ParNew收集器在年輕代中同樣也是采用復制算法、"Stop一 the一World"機制。
- ParNew是很多JVM運行在Server模式下新生代的默認垃圾收集器。

對於新生代,回收次數頻繁,使用並行方式高效。
對於老年代,回收次數少,使用串行方式節省資源。(CPU並行 需要切換線程,串行可以省去切換線程的資源)
ParNew收集器效率
-
由於ParNew收集器是基於並行回收,那么是否可以斷定ParNew收集器的回收效率在任何場景下都會比Serial收集器更高效?
- ➢ParNew 收集器運行在多CPU的環境下,由於可以充分利用多CPU、 多核心等物理硬件資源優勢,可以更快速地完成垃圾收集,提升程序的吞吐量。
- ➢但是在單個CPU的環境下,ParNew收 集器不比Serial收集器更高 效。雖然Serial收集器是基於串行回收,但是由於CPU不需要頻繁地做任務切換,因此可以有效避免多線程交互過程中產生的一些額外開銷。
-
除Serial Old外,目前ParNew GC還可以與CMS收集器配合工作
-
在程序中,開發人員可以通過選項"-XX: +UseParNewGC"手動指定使用.ParNew收集器執行內存回收任務。它表示年輕代使用並行收集器,不影響老年代。
-
-XX:ParallelGCThreads 限制線程數量,默認開啟和CPU數據相同的線程數。.
Parallel回收器:吞吐量優先
HotSpot的年輕代中除了擁有ParNew收集器是基於並行回收的以外, Parallel Scavenge收集器同樣也采用了復制算法、並行回收和"Stop the World"機制。那么Parallel收集器的出現是否多此一舉?
- ➢和ParNew收集器不同,Parallel Scavenge收集 器的目標則是達到一個可控制的吞吐量(Throughput),它也被稱為吞吐量優先的垃圾收集器。
- ➢自適應調節策略也是Parallel Scavenge 與ParNew一個重要區別。
Parallel Old收集器
- Parallel收集器在JDK1.6時提供了用於執行老年代垃圾收集的 Parallel Old收集器,用來代替老年代的Serial Old收集器。
- Parallel Old收集器采用了標記一壓縮算法,但同樣也是基於並行回收和”Stop一the一World"機制。
- 在程序吞吐量優先的應用場景中,Parallel 收集器和Parallel Old收集器的組合,在Server模式下的內存回收性能很不錯。
- 在Java8中,默認是此垃圾收集器

Parallel垃圾回收器參數配置
- -XX: +UseParallelGC手動指定 年輕代使用Parallel並行收集器執行內存回收任務。
- -XX: +UseParallel0ldGc手 動指定老年代都是使用並行回收收集器。
- 分別適用於新生代和老年代。默認jdk8是開啟的。
- 上面兩個參數,默認開啟一個,另一個也會被開啟。 (互相激活)
- -XX: ParallelGCThreads設置年輕代並行收集器的線程數。一般地,最好與CPU數量相等,以避免過多的線程數影響垃圾收集性能。
- 在默認情況下,當CPU數量小於8個, Paralle lGCThreads 的值等於CPU數量。
- 當CPU數量大於8個, ParallelGCThreads的值等於3+[5*CPU_ Count]/8]
- -XX :MaxGCPau3eMillis設置垃圾收集器最大停頓時間(即STw的時間)。單位是毫秒。
- ➢為了盡可能地把停頓時間控制在MaxGCPauseMills以內,收集器在.工作時會調整Java堆大小或者其他一些參數。
- ➢對於用戶來講,停頓時間越短體驗越好。但是在服務器端,我們注重 高並發,整體的吞吐量。所以服務器端適合Parallel,進行控制。➢該參數使用需謹慎。
- -XX:GCTimeRatio垃圾收集時間占總時間的比例(= 1 / (N + 1))用於衡量吞吐量的大小。
- ➢取值范圍(0, 100)。默認值99,也就是垃圾回收時間不超過1號。
- ➢與前一個-XX:MaxGCPauseMillis參數有一定矛盾性。暫停時間越長,Radio參數就容易超過設定的比例。
- -XX: +UseAdaptiveSizePolicy設 置Parallel Scavenge收 集器具有自適應調節策略
- 在這種模式下,年輕代的大小、Eden和Survivor的比例、晉升老年 代的對象年齡等參數會被自動調整,已達到在堆大小、吞吐量和停頓時間之間的平衡點。
- 在手動調優比較困難的場合,可以直接使用這種自適應的方式,僅指 定虛擬機的最大堆、目標的吞吐量(GCTimeRatio)和停頓時間(MaxGCPauseMills),讓虛擬機自己完成調優工作。
CMS回收器:低延遲(低的暫停時間)
在JDK1.5時期, HotSpot推出了一款在強交互應用中幾乎可認為有划 時代意義的垃圾收集器: CMS (Concurrent 一Mark 一 Sweep)收集器,這款收集器是HotSpot虛擬機中第一款真正意義上的並發收集器,它第一次實現了讓垃圾收集線程與用戶線程同時工作。
CMS收集器的關注點是盡可能縮短垃圾收集時用戶線程的停頓時間。停頓時 間越短(低延遲)就越適合與用戶交互的程序,良好的響應速度能提升用戶體驗。目前很大一部分的Java應用集中在互聯網站或者B/s系統的服務端上,這類應用尤其重視服務的響應速度,希望系統停頓時間最短,以給用戶帶來較好的體驗。CMS收集器就非常符合這類應用的需求。
- CMS的垃圾 收集算法采用標記一清除算法,並且也會" stop一the一world"
- 不幸的是,CMS 作為老年代的收集器,卻無法與JDK 1.4.0 中已經存在的新生代收集器Parallel Scavenge配合工作,所以在JDK 1. 5中使用CMS來收集老年代的時候,新生代只能選擇ParNew或者Serial收集器中的一個。
- 在G1出現之前,CMS使用還是非常廣泛的。一直到今天,仍然有很多系統使用CMS GC。
CMS執行過程
CMS整個過程比之前的收集器要復雜,整個過程分為4個主要階段,即初始標記階段、並發標記階段、重新標記階段和並發清除階段。
- 初始標記(Initial一Mark) 階段:STW||在這個階段中,程序中所有的工作線程都將會因為. “Stop一the一World"機制而出現短暫的暫停,這個階段的主要任務僅僅只是標記出GCRoots能直接關聯到的對象。一旦標記完成之后就會恢復之前被暫停的所有應用.線程。由於直接關聯對象比較小,所以這里的速度非常快。
- 並發標記(Concurrent一Mark)階段:從GC Roots的 直接關聯對象開始遍歷整個對象圖的過程,這個過程耗時較長但是不需要停頓用戶線程,可以與垃圾收集線程一起並發運行。
- 重新標記(Remark) 階段:STW||由於在並發標記階段中,程序的工作線程會和垃圾收集線程同時運行或者交叉運行,因此為了修正並發標記期間,因用戶程序繼續運作而導致標記產生變動的那一部分對象的標記記錄,這個階段的停頓時間通常會比初始標記階段稍長一些,但也遠比並發標記階段的時間短。
- 並發清除( Concurrent一Sweep)階段:此階段清理刪除掉標記階段判斷的已經死亡的對象,釋放內存空間。由於不需要移動存活對象,所以這個階段也是可以與用戶線程同時並發的
CMS特點分析
1.由於最耗費時間的並發標記與並發清除階段都不需要暫停工作,所以整體的回收是低停頓的。
答案其實很簡答,因為當並發清除的時候,用Compact整理內存的話,原來的用戶線程使用的內存還怎么用呢?要保證用戶線程能繼續執行,前提的它運行的資源不受影響嘛。Mark Compact更適合“Stop the World”這種場景”下使用
CMS優點.
- 並發收集
- 低延遲
CMS弊端
- 1)會產生內存碎片,導致並發清除后,用戶線程可用的空間不足。在無法分配大對象的情況下,不得不提前觸發Full GC。
- 2) CMS收集器對CPU資源非常敏感。在並發階段,它雖然不會導致用戶停頓,但是會因為占用了一部分線程而導致應用程序變慢,總吞吐量會降低。
- 3) CMS收集器無法處理浮動垃圾。可能出現“Concurrent Mode Failure" 失敗而導致另一次Full GC的產生。
- 浮動垃圾:在並發標記階段由於程序的工作線程和垃圾收集線程是同時運行或者交叉運行的,那么在並發標記階段如果產生新的垃圾對象,CMS將 無法對這些垃圾對象進行標記,最終會導致這些新產生的垃圾對象沒有被及時回收,從而只能在下一次執行GC時釋放這些之前未被回收的內存空間。
有人會覺得既然重新標記階段就包含垃圾修正了,那么為什么還會出現浮動垃圾呢?
CMS參數設置
- -XX:+UseConcMarkSweepGc 手動指定使用CMS收集器執行內存回收任務。
- ➢開啟該參數后會自動將一XX: +UseParNewGc打開。即: ParNew (Young區用) +CMS (0ld區用) +Serial 0ld的組合。
- -XX:CMS1ni tiatingOccupanyFraction設置堆內存使用率的閾值,一旦達到該閾值,便開始進行回收。
- JDK5及以前版本的默認值為68,即當老年代的空間使用率達到68號時,會執行一 .次CMS 回收。 JDK6 5及以上版本默認值為92號
- ➢如果內存增長緩慢,則可以設置一個稍大的值,大的閾值可以有效降低CMS的觸發頻率,減少老年代回收的次數可以較為明顯地改善應用程序性能。反之,如果應用程序內存使用率增長很快,則應該降低這個閾值,以避免頻繁觸發老年代串行收集器。因此通過該選項便可以有效降低Full GC的執行次數。
- -XX: +UseCMSCompactAtFullCollection用於指定在執行完Full GC后對內存空間進行壓縮整理,以此避免內存碎片的產生。不過由於內存壓縮整理過程無法並發執行,所帶來的問題就是停頓時間變得更長了。
- -XX:CMSFullGCsBeforeCompaction設置在執行多少次Full GC后對內存空間進行壓縮整理。
- -XX:ParallelCMSThreads 設置CMS的線程數量。
- CMS 默認啟動的線程數是(ParallelGCThreads+3) /4, ParallelGCThreads是年輕代並行收集器的線程數。當CPU資源比較緊張時,受到CMs收集器線程的影響,應用程序的性能在垃圾回收階段可能會非常糟糕。
JDK 后續版本中CMS的變化
- JDK9新特性: CMS被標記為Deprecate了(JEP291)
- 如果對JDK 9及以上版本的HotSpot虛擬機使用參數-XX:+UseConcMarkSweepGC來開啟CMS收集器的話,用戶會收到一個警告信息,提示CMS未來將會被廢棄。
- JDK14新特性: 刪除CMS垃圾回收器(JEP363)
- 移除了CMS垃圾收集器,如果在JDK14中使用-XX: +UseConcMarkSweepGC的話,JVM不會報錯,只是給出一個warning信息,但是不會exit。JVM會自動回退以默認GC方式啟動JVM
垃圾回收器上篇小結
請記住以下口令:
如果你想要最小化地使用內存和並行開銷,請選Serial GC;
如果你想要最大化應用程序的吞吐量,請選Parallel GC;
如果你想要最小化GC的中斷或停頓時間,請選CMS GC。