深入理解Java虛擬機(四)垃圾收集器


如果垃圾回收算法時內存回收的方法論,那麼垃圾收集器就是內存回收的具體體現。

Java規範中對垃圾收集器應該如何實現並沒有任何規定,因此不同的廠商、不同版本的虛擬機所提供的垃圾收集器都可能會有很大的差別,並且一般都會提供參數根據用戶自己的應用特點和要求組合出各個年代所使用的收集器。下面我描述的收集器是基於JDK1.7的G1收集器之後的HotSpot虛擬機,這個JVM包含的所有收集器如下圖所示:
在這裏插入圖片描述
上面展示了7種作用於不同分代的收集器,如果兩個收集器之間存在連線,就說明它們之間可以搭配使用。所處的區域,便是它們是屬於新生代收集器還是老年代收集器。

0. 基本概念

  • 並行(Parallel): 指多條垃圾收集線程並行工作,但此時用戶線程仍處於等待狀態。
  • 併發(Concurrent): 指用戶線程與垃圾收集器同時執行(並不一定並行,可能會交替執行),用戶線程繼續運行,而垃圾收集程序在另外一個CPU上。
  • 吞吐量就是CPU用於運行用戶代碼的時間CPU總消耗時間的比值。
    吞吐量 = 運行用戶代碼的時間 / (運行用戶代碼的時間 + 垃圾收集時間)
     在這裏舉個例子解釋一下:假設虛擬機總共運行了100分鐘,其中垃圾收集花了1分鐘,那吞吐量就是99%。

1. 新生代收集器

1.1 Serial收集器(串行GC)

Serial是最基本、發展歷史最悠久的收集器,曾經(在JDK1.3.1之前)是虛擬機新生代收集的唯一選擇。
在這裏插入圖片描述

  1. 特性:
  • 單線程的收集器
    它的“單線程”的意義並不僅僅說明它只會使用一個CPU或者一條收集線程去完成垃圾收集工作,更重要的是在它進行垃圾收集時,必須暫停其他所有的工作線程,直至它收集結束。
  1. 應用場景
    Serial收集器時虛擬機運行在Client模式下的默認的新生代收集器

Cilent模式:加載速度較快,爲在客戶端環境中減少啓動時間而優化;比較適合桌面程序,它會做一些例如像快速初始化,懶加載這一類的事件來適應桌面程序的特點。《該模式JDK1.8沒有了》

Server模式:
加載速度較慢但運行起來較快,爲在服務器環境中最大化程序執行速度而設計;適合做服務器程序,一些針對服務器特點的事情,比如預加載,尤其在一些併發的處理上,是會做更多的優化。《它不需要可視化界面,參數已調優》

  1. 優勢
      與其他收集器的單線程相比,簡單而高效,對於限定單個CPU的環境來看,Serial收集器由於沒有線程交互的開銷,專心做垃圾收集器自然可以獲得最高的單線程收集效率。實際上到現在爲止:它仍然是虛擬機運行在Client模式下的默認新生代收集器。

1.2 ParNew收集器(並行GC)

ParNew收集器就是Serial收集器的多線程版本,除了使用多條線程進行垃圾收集之外,其餘行爲包括Serial收集器可用的所有控制字參數、收集算法、Stop the World(在它進行垃圾收集的時候,必須暫停其他所有工作線程,直至它收集結束)、對象分配規則、回收策略等都與Serial收集器完全一樣。
在這裏插入圖片描述

  1. 特性
    Serial收集器的多線程版本。
  2. 應用場景
    ParNew收集器是許多運行在Server模式下的虛擬機中首選的新生代收集器。
     作爲Server的首選收集器之中有一個與性能無關的很重要的原因是:除Serial收集器外,目前只有它能與CMS收集器配合工作。在JDK1.5時期,HotSpot推出了一款強交互應用中幾乎可認爲劃時代意義的垃圾收集器——CMS收集器,這款收集器是HotSpot虛擬機中第一款真正意義上的併發收集器,他第一次實現了讓垃圾收集線程與用戶線程同時工作。不幸的是,CMS作爲老年代的收集器,卻無法與JDK1.4.0已經存在的新生代收集器Parallel Scavenge配合工作,所以在JDK1.5中使用CMS來收集老年代的時候,新生代只能選擇ParNew或者Serial收集器中的一個。
  3. 對比分析
    與Serial收集器對比:
     ParNew收集器在單CPU的環境中絕對不會有比Serial收集器更好的效果,甚至由於存在線程交互的開銷,該手機其再通過超線程技術實現的兩個CPU的環境中都不能百分之百保證可以超越Serial收集器。然而隨着可以使用CPU的數量增加,它對於GC系統資源的有效利用還是很有好處的。

1.3 Parallel Scavenge收集器(並行GC)

在這裏插入圖片描述

  1. 特性
    Parallel Scavenge收集器是一個新生代收集器,它是使用複製算法的收集器,又是並行多線程收集器。
    Parallel Scavenge收集器是一個可以控制吞吐量的收集器,通過下面兩個參數實現:
XX:MaxGCPauseMills:控制最大的垃圾收集停頓時間
XX:GCRatio:直接設置吞吐量大小

  直觀上,只要最大的垃圾收集停頓時間越小,吞吐量是越高的,但是GC停頓時間的縮短是以犧牲吞吐量和新生代空間作爲代價的。 比如原來的10秒收集一次,每次收集停頓100毫秒,現在變成5秒收集一次,每次停頓70毫秒。停頓時間下降的同時,吞吐量也下降了。

  1. 應用場景
    停頓時間越短就越是和需要與用戶交互的程序,良好的響應速度能提升用戶體驗,而高吞吐量則可以高效地利用CPU時間,儘快完成程序的運算任務,主要適合在後臺運算而不需要太多交互的任務。
  2. 對比分析
     - Parallel Scavenge收集器 VS CMS等收集器: Parallel Scavenge收集器的特點是:它的關注點與其他收集器不同,
    CMS收集器的關注點是儘可能的縮短垃圾收集時的用戶線程的停頓時間
    Parallel Scavenge收集器的目標則是可達到一個可控制的吞吐量(Throughtput)
    由於與吞吐量關係密切,Parallele Scavenge收集器也經常稱爲“吞吐量收集器”。
     - Parallel Scavenge收集器 VS ParNew收集器: Parallel Scavenge收集器與ParNew收集器的一個重要的區別是它具有自適應調節策略。
  3. GC自適應調節策略
    Parallel Scavenge收集器有一個參數- XX:+UseAdaptiveSizePolicy。當這個參數打開之後,就不需要手工指定新生代的大小、Eden和Survivor區的比例、晉升老年代對象年齡等細節參數了,虛擬機會根據當前系統的運行情況收集性能監控信息,動態調整這些參數以提供最合適的停頓時間或者最大的吞吐量,這種調節方法稱爲GC自適應調節策略(GC Ergonomics)。

2. 老年代收集器

2.1 Serial Old收集器(串行GC)

在這裏插入圖片描述

  1. 特性
    Serial Old是Serial收集器的老年代版本。它同樣是一個單線程收集器,使用標記-整理算法
  2. 應用場景
       - Client模式 Serial Old收集器的主要意義也是在與給Client模式下的虛擬機使用。
       - Server模式 如果在Server模式下,那麼它的主要還有兩大用途:一種用途是在JDK1.5以及之前的版本中與Parallel Scavenge收集器搭配使用;另一種用途就是作爲CMS收集器的後備預案,在併發收集發生Concurrent Mode Faliure時使用。

2.2 Parallel Old收集器(並行GC)

在這裏插入圖片描述

  1. 特性
    Parallel Old是Parallel Scavenge收集器的老年代版本,使用多線程和**“標記-整理”算法**。
  2. 應用場景
    注重吞吐量以及CPU資源敏感的場合,都可以優先考慮Parallel Scavenge加Parallel Old收集器。這個收集器是在JDK 1.6中才開始提供的,在此之前,新生代的Parallel Scavenge收集器一直處於比較尷尬的狀態。原因是,如果新生代選擇了Parallel Scavenge收集器,老年代除了Serial Old收集器外別無選擇(Parallel Scavenge收集器無法與CMS收集器配合工作)。由於老年代Serial Old收集器在服務端應用性能上的“拖累”,使用了Parallel Scavenge收集器也未必能在整體應用上獲得吞吐量最大化的效果,由於單線程的老年代收集中無法充分利用服務器多CPU的處理能力,在老年代很大而且硬件比較高級的環境中,這種組合的吞吐量甚至還不一定有ParNew加CMS的組合“給力”。直到Parallel Old收集器出現後,“吞吐量優先”收集器終於有了比較名副其實的應用組合。

2.3 CMS收集器(併發GC)

在這裏插入圖片描述

  1. 特性
    CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器。目前很大一部分的Java應用集中在互聯網站或者B/S系統的服務端上,這類應用尤其重視服務的響應速度,希望系統停頓時間最短,以給用戶帶來較好的體驗。CMS收集器就非常符合這類應用的需求。
    CMS收集器是基於“標記—清除”算法實現的,它的運作過程相對於前面幾種收集器來說更復雜一些,整個過程分爲4個步驟:
  • 初始標記(CMS initial mark) 初始標記僅僅只是標記一下GC Roots能直接關聯到的對象,速度很快,需要“Stop The World”。
  • 併發標記(CMS concurrent mark) 併發標記階段就是進行GC Roots Tracing的過程。
  • 重新標記(CMS remark) 重新標記階段是爲了修正併發標記期間因用戶程序繼續運作而導致標記產生變動的那一部分對象的標記記錄,這個階段的停頓時間一般會比初始標記階段稍長一些,但遠比並發標記的時間短,仍然需要“Stop The World”。
  • 併發清除(CMS concurrent sweep) 併發清除階段會清除對象。

由於整個過程中耗時最長的併發標記和併發清除過程收集器線程都可以與用戶線程一起工作 所以,從總體上來說, CMS收集器的內存回收過程是與用戶線程一起併發執行的。

  1. 優點: CMS是一款優秀的收集器,它的主要優點在名字上已經體現出來了:併發收集、低停頓。
  2. 缺點:
  • CMS收集器對CPU資源非常敏感。其實,面向併發設計的程序都對CPU資源比較敏感。在併發階段,它雖然不會導致用戶線程停頓,但是會因爲佔用了一部分線程(或者說CPU資源)而導致應用程序變慢,總吞吐量會降低CMS默認啓動的回收線程數是(CPU數量+3)/ 4,也就是**當CPU在4個以上時,併發回收時垃圾收集線程不少於25%的CPU資源,並且隨着CPU數量的增加而下降。**但是當CPU不足4個(譬如2個)時,CMS對用戶程序的影響就可能變得很大。
  • CMS收集器無法處理浮動垃圾。CMS收集器無法處理浮動垃圾,可能出現“Concurrent Mode Failure”失敗而導致另一次Full GC的產生。 由於CMS併發清理階段用戶線程還在運行着,伴隨程序運行自然就還會有新的垃圾不斷產生,這一部分垃圾出現在標記過程之後,CMS無法在當次收集中處理掉它們,只好留待下一次GC時再清理掉。這一部分垃圾就稱爲“浮動垃圾”。也是由於在垃圾收集階段用戶線程還需要運行,那也就還需要預留有足夠的內存空間給用戶線程使用,因此CMS收集器不能像其他收集器那樣等到老年代幾乎完全被填滿了再進行收集,需要預留一部分空間提供併發收集時的程序運作使用。要是CMS運行期間預留的內存無法滿足程序需要,就會出現一次“Concurrent Mode Failure”失敗,這時虛擬機將啓動後備預案:臨時啓用Serial Old收集器來重新進行老年代的垃圾收集,這樣停頓時間就很長了。
  • CMS收集器會產生大量空間碎片。CMS是一款基於“標記—清除”算法實現的收集器,這意味着收集結束時會有大量空間碎片產生。空間碎片過多時,將會給大對象分配帶來很大麻煩,往往會出現老年代還有很大空間剩餘,但是無法找到足夠大的連續空間來分配當前對象,不得不提前觸發一次Full GC。

3. G1收集器(唯一一款全區域的垃圾回收器)

G1(Garbage First)垃圾回收器是用在heap memory很大的情況下,把heap劃分爲很多很多的region塊,然後並行的對其進行垃圾回收。
G1垃圾回收器在清除實例所佔用的內存空間後,還會做內存壓縮。
G1垃圾回收器回收region的時候基本不會STW(停止用戶線程),而是基於 most garbage優先回收(整體來看是基於"標記-整理"算法,從局部(兩個region之間)基於"複製"算法) 的策略來對region進行垃圾回收的。
  無論如何,G1收集器採用的算法都意味着:一個region有可能屬於Eden,Survivor或者Tenured內存區域。圖中的

  • E表示該region屬於Eden內存區域,
  • S表示屬於Survivor內存區域,
  • T表示屬於Tenured內存區域。
  • 圖中空白的表示未使用的內存空間。
  • G1垃圾收集器還增加了一種新的內存區域,叫做Humongous內存區域,如圖中的H塊。這種內存區域主要用於存儲大對象-即大小超過一個region大小的50%的對象。

結果如下圖所示:
在這裏插入圖片描述
年輕代垃圾收集
  在G1垃圾收集器中,年輕代的垃圾回收過程使用複製算法。把Eden區和Survivor區的對象複製到新的Survivor區域。如下圖:
在這裏插入圖片描述
老年代收集
  對於老年代上的垃圾收集,G1垃圾收集器也分爲4個階段,基本跟CMS垃圾收集器一樣,但略有不同:

  • 初始標記(Initial Mark)階段 :同CMS垃圾收集器的Initial Mark階段一樣,G1也需要暫停應用程序的執行,它會標記從根對象出發,在根對象的第一層孩子節點中標記所有可達的對象。但是G1的垃圾收集器的Initial Mark階段是跟minor gc一同發生的。也就是說,在G1中,你不用像在CMS那樣,單獨暫停應用程序的執行來運行Initial Mark階段,而是在G1觸發minor gc的時候一併將年老代上的Initial Mark給做了。
  • 併發標記(Concurrent Mark)階段 :在這個階段G1做的事情跟CMS一樣。但G1同時還多做了一件事情,就是如果在Concurrent Mark階段中,發現哪些Tenured region中對象的存活率很小或者基本沒有對象存活,那麼G1就會在這個階段將其回收掉,而不用等到後面的clean up階段。這也是Garbage First名字的由來。同時,在該階段,G1會計算每個 region的對象存活率,方便後面的clean up階段使用 。
  • 最終標記(CMS中的Remark階段) : 在這個階段G1做的事情跟CMS一樣, 但是採用的算法不同,G1採用一種叫做SATB(snapshot-at-the-begining)的算法能夠在Remark階段更快的標記可達對象。
  • 篩選回收(Clean up/Copy)階段 : 在G1中,沒有CMS中對應的Sweep階段。相反 它有一個Cleanup/Copy階段,在這個階段中,G1會挑選出那些對象存活率低的region進行回收,這個階段也是和minor gc一同發生的。

如下圖所示:
在這裏插入圖片描述
G1(Garbage-First)是一款面向服務端應用的垃圾收集器。HotSpot開發團隊賦予它的使命是未來可以替換掉JDK1.5中發佈的CMS收集器。 如果你的應用追求低停頓,G1可以作爲選擇;如果你的應用追求吞吐量,G1並不帶來特別明顯的好處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章