垃圾收集器

如果說垃圾回收算法是內存回收的方法論,那麼垃圾收集器就是內存回收的具體實現,java虛擬機實現規範中對垃圾收集器應該如何實現並沒有任何規定,因此不同廠商、不同版本的虛擬機提供的垃圾收集器存在很大的差異,接下來我們主要介紹一下HOTSPOT虛擬機中的幾種垃圾收集器。這個虛擬包含的收集器如下圖:

                                

上圖中7中作用於不同分代的收集器,如果兩個之間存在連線,就說明他們可以搭配使用。收集器所處於的區域,則代表它是屬於新生代收集器還是老年代收集器。

併發垃圾收集和並行垃圾收集 

在介紹垃圾收集器前,我們首先認識兩個概念,並行垃圾收集和併發垃圾收集

(A)、並行(Parallel)

       指多條垃圾收集線程並行工作,但此時用戶線程仍然處於等待狀態;

       如ParNew、Parallel Scavenge、Parallel Old;

(B)、併發(Concurrent)

       指用戶線程與垃圾收集線程同時執行(但不一定是並行的,可能會交替執行);

      用戶程序在繼續運行,而垃圾收集程序線程運行於另一個CPU上;    

       如CMS、G1(也有並行);

 

新代垃圾收集器

Serial 垃圾收集器(單線程)

大家看名字就知道這是一個 單線程的收集器,但它的“單線程”並不僅僅說明它只會使用一個CPU或者一條收集線程去完成垃圾回收工作,更重要的是它在進程垃圾回收的時候,必須暫停其他所有的工作線程(Stop The World),直到它收集結束。在用戶不知情的情況下,暫停了用戶所有的線程,這聽起來實在是不可思議。那麼這個線程是不是現在就被放棄使用了。 並不是這樣,該收集現在仍然是運行在Client模式下的虛擬機的默認新生代垃圾收集器。該收集器的運行過程如下:

ParNew 垃圾收集器(多線程) 

ParNew收集器其實就是serial收集器的多線程的版本,除了使用多線程進行垃圾收集之外,其餘的行爲包括控制參數和收集算法,STW,對象分配規則,回收策略 等都與Serial收集器完全一樣,在實現上兩個收集器也共用了很多的代碼。

雖然ParNew收集器和Serial收集器沒有太多的創新之處,但是他卻是 很多運行在Server 模型下的虛擬機中首先新生代 收集器,其中一個與性能沒有關係的但是 很重要的一個原因是,目前除了Serial 收集器 外,目前只有它能與CMS收集器配合工作。

Parallel Scavenge 垃圾收集器(多線程)

Parallel Scavenge 收集器也是 新生代收集器,它也是採用的複製算法,又是並行的多線程收集器,看上去和ParNew收集器差不多,但是這兩者有很大的差別。

1、Parallel Scavenge 收集器追求CPU吞吐量,能夠在較短時間內完成垃圾收集任務,因此適合沒有交互的後臺計算,而ParNew:追求降低用戶停頓時間,適合交互式應用。

吞吐量 = 運行用戶代碼時間 / (運行用戶代碼時間 + 垃圾收集時間)

追求高吞吐量,可以通過減少 GC 執行實際工作的時間,然而,僅僅偶爾運行 GC 意味着每當 GC 運行時將有許多工作要做,因爲在此期間積累在堆中的對象數量很高。單個 GC 需要花更多的時間來完成,從而導致更高的暫停時間。而考慮到低暫停時間,最好頻繁運行 GC 以便更快速完成,反過來又導致吞吐量下降。

  • 通過參數 -XX:GCTimeRadio 設置垃圾回收時間佔總 CPU 時間的百分比。
  • 通過參數 -XX:MaxGCPauseMillis 設置垃圾處理過程最久停頓時間。
  • 通過命令 -XX:+UseAdaptiveSizePolicy 開啓自適應策略。我們只要設置好堆的大小和 MaxGCPauseMillis 或 GCTimeRadio,收集器會自動調整新生代的大小、Eden 和 Survivor 的比例、對象進入老年代的年齡,以最大程度上接近我們設置的 MaxGCPauseMillis 或 GCTimeRadio。

老年代垃圾收集器

Serial Old垃圾收集器(單線程)

Serial Old收集器是Serial收集器的老年代版本,同樣是單線程收集器, 使用“標記-整理”算法。這個收集器的意義也是在client模式下使用。如果在Server模式下,那麼它主要由兩大 用途:一種是在JDK1.5之前版本中與Paralle Scavenge收集器搭配使用,另一種用途是作爲CMS收集器的後備預案,在併發收集發生Concurrent model failure時使用。

 Parallel Old 垃圾收集器(多線程)

Parallel Old收集器是Parallel收集器的老年代版本, 使用多線程和“標記-清除算法”。這個收集器是在JDK1.6纔開始提供的,再次之前新生代收集器 Parallel Scavenge收集器處於一個比較尷尬的地位,因爲新生代選擇了Parallel Scavenge收集器老年代只能選擇Serial Old收集器。由於Serial Old在性能上的拖累,使用了Parallel Scavenge收集器也未必能在整體應用上獲得吞吐量最大化的效果,由於單線程的老年代收集中無法充分利用服務器的多CPU的處理能力,在老年代很大並且硬件條件比較高級的環境,這種組合的吞吐量甚至不一定有ParNew+CMS的組合給力。

 CMS 垃圾收集器

CMS垃圾收集器是一種以 獲取最短回收  停頓時間爲目標的收集器。目前 很大一部分的java應集中在 互聯網或者B/S系統服務端上。CMS收集器是基於“標記-清除”算法實現的,它的運作過程分爲下面幾個部分:

  • 初始標記:Stop The World,僅使用一條初始標記線程對所有與 GC Roots 直接關聯的對象進行標記。
  • 併發標記:使用多條標記線程,與用戶線程併發執行。此過程進行可達性分析,標記出所有廢棄對象。速度很慢。
  • 重新標記:Stop The World,使用多條標記線程併發執行,將剛纔併發標記過程中新出現的廢棄對象標記出來。
  • 併發清除:只使用一條 GC 線程,與用戶線程併發執行,清除剛纔標記的對象。這個過程非常耗時。

 CMS是一款優秀的收集器,它的主要優點已經體現出來了:併發收集,低停頓,Sun公司的一些官方文檔也稱之爲併發低停頓收集器。當然,CMS收集器也並不是 一個十全十美的收集器,它的缺點主要包括 下面幾個方面:

  • 吞吐量低
  • 無法處理浮動垃圾,導致頻繁 Full GC
  • 使用“標記-清除”算法產生碎片空間

對於產生碎片空間的問題,可以通過開啓 -XX:+UseCMSCompactAtFullCollection,在每次 Full GC 完成後都會進行一次內存壓縮整理,將零散在各處的對象整理到一塊。設置參數 -XX:CMSFullGCsBeforeCompaction告訴 CMS,經過了 N 次 Full GC 之後再進行一次內存整理。

G1通用收集器

G1 是一款面向服務端應用的垃圾收集器,它沒有新生代和老年代的概念,而是將堆劃分爲一塊塊獨立的 Region。當要進行垃圾收集時,首先估計每個 Region 中垃圾的數量,每次都從垃圾回收價值最大的 Region 開始回收,因此可以獲得最大的回收效率。

從整體上看, G1 是基於“標記-整理”算法實現的收集器,從局部(兩個 Region 之間)上看是基於“複製”算法實現的,這意味着運行期間不會產生內存空間碎片。

這裏拋個問題:
一個對象和它內部所引用的對象可能不在同一個 Region 中,那麼當垃圾回收時,是否需要掃描整個堆內存才能完整地進行一次可達性分析?

並不!每個 Region 都有一個 Remembered Set,用於記錄本區域中所有對象引用的對象所在的區域,進行可達性分析時,只要在 GC Roots 中再加上 Remembered Set 即可防止對整個堆內存進行遍歷。

如果不計算維護 Remembered Set 的操作,G1 收集器的工作過程分爲以下幾個步驟:

  • 初始標記:Stop The World,僅使用一條初始標記線程對所有與 GC Roots 直接關聯的對象進行標記。
  • 併發標記:使用一條標記線程與用戶線程併發執行。此過程進行可達性分析,速度很慢。
  • 最終標記:Stop The World,使用多條標記線程併發執行。
  • 篩選回收:回收廢棄對象,此時也要 Stop The World,並使用多條篩選回收線程併發執行。

小結

HotSpot 虛擬機提供了多種垃圾收集器,每種收集器都有各自的特點,雖然我們要對各個收集器進行比較,但並非爲了挑選出一個最好的收集器。我們選擇的只是對具體應用最合適的收集器。

發佈了364 篇原創文章 · 獲贊 389 · 訪問量 141萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章