JVM基礎系列第9講:JVM垃圾回收器


前面文章中,我們介紹了 Java 虛擬機的內存結構,Java 虛擬機的垃圾回收機制,那么這篇文章我們說說具體執行垃圾回收的垃圾回收器。

總的來說,Java 虛擬機的垃圾回收器可以分為四大類別:串行回收器、並行回收器、CMS 回收器、G1 回收器。

串行回收器

串行回收器是指使用單線程進行垃圾回收的回收器。因為每次回收時只有一個線程,因此串行回收器在並發能力較弱的計算機上,其專注性和獨占性的特點往往能讓其有更好的性能表現。

串行回收器可以在新生代和老年代使用,根據作用於不同的堆空間,分為新生代串行回收器和老年代串行回收器。

新生代串行回收器

串行收集器是所有垃圾回收器中最古老的一種,也是 JDK 中最基本的垃圾回收器之一。

在新生代串行回收器中使用的是復制算法。在串行回收器進行垃圾回收時,會觸發 Stop-The-World 現象,即其他線程都需要暫停,等待垃圾回收完成。因此在某些情況下,其會造成較為糟糕的用戶體驗。

使用 -XX:+UseSerialGC 參數可以指定使用新生代串行收集器和老年代串行收集器。當虛擬機在 Client 模式下運行時,其默認使用該垃圾收集器。

老年代串行回收器

在老年代串行回收器中使用的是標記壓縮算法。其與新生代串行收集器一樣,只能串行、獨占式地進行垃圾回收,因此也經常會有較長時間的 Stop-The-World 發生。

但老年代串行回收器的好處之一,就是其可以與多種新生代回收器配合使用。若要啟用老年代串行回收器,可以嘗試以下參數:

  • -XX:UseSerialGC:新生代、老年代都使用串行回收器。
  • -XX:UseParNewGC:新生代使用 ParNew 回收器,老年代使用串行回收器。
  • -XX:UseParallelGC:新生代使用 ParallelGC 回收器,老年代使用串行回收器。

並行回收器

並行回收器在串行回收器的基礎上做了改進,其使用多線程進行垃圾回收。對於並行能力強的機器,可以有效縮短垃圾回收所使用的時間。

根據作用內存區域的不同,並行回收器也有三個不同的回收器:新生代 ParNew 回收器、新生代 ParallelGC 回收器、老年代 ParallelGC 回收器。

新生代 ParNew 回收器

新生代 ParNew 回收器工作在新生代,其只是簡單地將串行回收器多線程化,其回收策略、算法以及參數和新生代串行回收器一樣。

新生代 ParNew 回收器同樣使用復制的垃圾回收算法,其垃圾收集過程中同樣會觸發 Stop-The-World 現象。但因為其使用多線程進行垃圾回收,因此在並發能力強的 CPU 上,其產生的停頓時間要短於串行回收器。

但在單 CPU 或並能能力弱的系統中,並行回收器效果會因為線程切換的原因,其實際表現反而不如串行回收器。

要開啟新生代 ParNew 回收器,可以使用以下參數:

  • -XX:+UseParNewGC:新生代使用 ParNew 回收器,老年代使用串行回收器。
  • -XX:UseConcMarkSweepGC:新生代使用 ParNew 回收器,老年代使用 CMS。
  • -XX:ParallelGCThreads:指定 ParNew 回收器的工作線程數量。

新生代 Parallel GC 回收器

新生代 Parallel GC 回收器與新生代 ParNew 回收器非常類似,其也是使用復制算法,都是多線程、獨占式的收集器,也會導致 Stop-The-World。但其余 ParNew 回收器的一個重大不同是:其非常注重系統的吞吐量。

之所以說新生代 Parallel GC 回收器非常注重系統吞吐量,是因為其有一個自適應 GC 調節策略。我們可以使用 -XX:+UseAdaptiveSizePolicy 參數打開這個策略,在這個模式下,新生代的大小、Eden 和 Survivor 的比例、晉升老年代的對象年齡等參數都會被自動調節,已達到堆大小、吞吐量、停頓時間的平衡點。

Parallel GC 回收器提供了兩個重要參數用於控制系統的吞吐量。

  • -XX:MaxGCPauseMillis:設置最大垃圾收集停頓時間。在 ParallelGC 工作時,其會自動調整響應參數,將停頓時間控制在設置范圍內。為了達到目的,其可能會使用較小的堆,但這會導致 GC 較為頻繁。
  • -XX:GCTimeRatio:設置吞吐量大小,其實一個 0 - 100 的整數。假設 GCTimeRatio 的值為 n,那么系統將不花費超過 1/(1+n) 的時間用於垃圾手機。比如 GCTimeRatio 值為 19,那么系統用於垃圾收集的時間不超過 1 /(1+19) = 5%。默認情況下,它的取值是 99,即不超過 1% 的時間用於垃圾收集。

新生代 Parallel GC 回收器可以使用以下參數啟用:

  • -XX:+UseParallelGC:新生代使用 Parallel 回收器,老年代使用串行回收器。
  • -XX:+UseParallelOldGC:新生代使用 ParallelGC 回收器,老年代使用 ParallelOldGC 回收器。

老年代 ParallelOldGC 回收器

老年代 ParallelOldGC 回收器也是一種多線程並發的回收器,與新生代 ParallelGC 收集器一樣,其也是注重吞吐量的收集器,只不過其是作用於老年代。

ParallelOldGC 回收器使用的是標記壓縮算法,只有在 JDK 1.6 中才可以使用。我們可以使用-XX:UseParallelOldGC參數在新生代中使用 ParallelGC 收集器,在老年代中使用 ParallelOldGC 收集器。參數 -XX:ParallelGCThreads也可以用於設置垃圾回收時的線程數量。

CMS 回收器

與 ParallelGC 和 ParallelOldGC 不同,CMS 回收器主要關注系統停頓時間。CMS 回收器全稱為 Concurrent Mark Sweep,意為標記清除算法,其是一個使用多線程並行回收的垃圾回收器。

工作步驟

CMS 的主要工作步驟有:初始標記、並發標記、預清理、重新標記、並發清除和並發充值。其中初始標記和重新標記是獨占系統資源的,而其他階段則可以和用戶線程一起執行。

在整個 CMS 回收過程中,默認情況下會有預清理的操作,我們可以關閉開關 -XX:-CMSPrecleaningEnabled 不進行預清理。因為重新標記是獨占 CPU 的,因此如果新生代 GC 發生之后,立刻出發一次新生代 GC,那么停頓時間就會很長。為了避免這種情況,預處理時會刻意等待一次新生代 GC 的發生,之后在進行預處理。

主要參數

啟動 CMS 回收器刻意使用參數:-XX:+UseConcMarkSweepGC,線程並發數量刻意通過 -XX:ConcGCThreads-XX:ParallelCMSThreads 參數設定。

此外,我們還可以設置 -XX:CMSInitiatingOccupancyFraction 來指定老年代空間使用閾值。當老年代空間使用率達到這個閾值時,會執行一次 CMS 回收,而不像其他回收器一樣等到內存不夠用的時候才進行 GC。

我們之前說過標記清除算法的缺點是會產生內存碎片,因此 CMS 回收器會產生較多內存碎片。我們可以使用 XX:+UseCMSCompactAtFullCollection 參數讓 CMS 在完成垃圾回收后,進行一次內存碎片整理。使用 -XX:CMSFullGCsBeforeCompaction 參數設置進行多少次 CMS 回收后,進行一次內存壓縮。

此外,如果希望使用 CMS 回收 Perm 區,那么則可以打開 -XX:+CMSClassUnloadingEnabled 開關。打開該開關后,如果條件允許,那么系統會使用 CMS 的機制回收 Perm 區 Class 數據。

G1 回收器

G1 回收器是 JDK 1.7 中使用的全新垃圾回收器,從長期目標來看,其是為了取代 CMS 回收器。

G1 回收器擁有獨特的垃圾回收策略,和之前所有垃圾回收器采用的垃圾回收策略不同。從分代看,G1 依然屬於分代垃圾回收器。但它最大的改變是使用了分區算法,從而使得 Eden 區、From 區、Survivor 區和老年代等各塊內存不必連續。

在 G1 回收器之前,所有的垃圾回收器其內存分配都是連續的一塊內存,如下圖所示。

而在 G1 回收器中,其將一大塊的內存分為許多細小的區塊,從而不要求內存是連續的。

從上圖可以看到,每個Region被標記了 E、S、O 和 H,說明每個 Region 在運行時都充當了一種角色。所有標記為 E 的都是 Eden 區的內存,它們散落在內存的各個角落,並不要求內存連續。同理,Survivor 區、老年代(Old)也是如此。

從上圖我們還可以看到 H 是以往算法中沒有的,它代表 Humongous。這表示這些 Region 存儲的是巨型對象(humongous object,H-obj),當新建對象大小超過 Region 大小一半時,直接在新的一個或多個連續 Region 中分配,並標記為 H。

堆內存中一個 Region 的大小可以通過 -XX:G1HeapRegionSize 參數指定,大小區間只能是1M、2M、4M、8M、16M 和 32M,總之是2的冪次方。如果G1HeapRegionSize 為默認值,即把設置的最小堆內存按照2048份均分,最后得到一個合理的大小。

工作步驟

G1 收集器的收集過程主要有四個階段:

  • 新生代 GC
  • 並發標記周期
  • 混合收集
  • 如果需要,可能進行 FullGC

新生代 GC 與其他垃圾收集器的類似,就是清空 Eden 區,將存活對象移動到 Survivor 區,部分年齡到了就移動到老年代。

並發標記周期則分為:初始標記、根區域掃描、並發標記、重新標記、獨占清理、並發清理階段。其中初始標記、重新標記、獨占清理是獨占式的,會引起停頓。並且初始標記會引發一次新生代 GC。在這個階段,所有將要被回收的區域會被 G1 記錄在一個稱之為 Collection Set 的集合中。

混合回收階段會首先針對 Collection Set 中的內存進行回收,因為這些垃圾比例較高。G1 回收器的名字 Garbage First 就是這個意思,垃圾優先處理的意思。在混合回收的時候,也會執行多次新生代 GC 和 混合 GC,從而來進行內存的回收。

必要時進行 Full GC。當在回收階段遇到內存不足時,G1 會停止垃圾回收並進行一次 Full GC,從而騰出更多空間進行垃圾回收。

相關參數

打開 G1 收集器,我們可以使用參數:`-XX:+UseG1GC。

設置目標最大停頓時間,可以使用參數:-XX:MaxGCPauseMillis

設置 GC 工作線程數量,可以使用參數:-XX:ParallelGCThreads

設置堆使用率觸發並發標記周期的執行,可以使用參數:-XX:InitiatingHeapOccupancyPercent

總結

從一開始的串行回收器,到后來的並行回收器、CMS回收器,到最后的 G1 回收器,垃圾回收器不斷改進,使得垃圾回收效率不斷提升。特別是分區思想誕生后,對於垃圾回收停頓時間的控制更加細膩,可以讓應用有更完美的延時控制,從而呈現更好的用戶體驗。

參考資料

G1 垃圾收集器介紹 - ImportNew


如果只是看,其實無法真正學會知識的。為了幫助大家更好地學習,我建了一個虛擬機群,專門討論學習 Java 虛擬機方面的內容,每周針對我所發文章進行討論答疑。如果你有興趣,關注「Java技術精選」公眾號,通過右下角菜單「入群交流」加我好友,小助手會拉你入群。



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM