JVM幾種垃圾回收器介紹


整理自:http://www.cnblogs.com/lspz/p/6397649.html

一、如何回收?

1.1 垃圾收集算法:

(1)標記-清除(Mark-Sweep)算法

這是最基礎的算法,就像它名字一樣,算法分為“標記”和“清除”兩個階段:首先標記處所有需要回收的對象(如哪些內存需要回收所描述的對象),對標記完成后統一回收所有被標記的對象,如下圖所示:

 

缺點:一個是效率問題,標記和清除兩個過程的效率都不高;另一個是空間問題,標記清除后悔產生大量的不連續的內存碎片,可能會導致后續無法分配大對象而導致再一次觸發垃圾收集動作。

(2)復制算法

為了針對標記-清除算法的不足,復制算法將可用內存容量划分為大小相等的兩塊,每次只使用一塊。當一塊的內存用完了,就將還存活的對象復制到另一塊上面去。然后把已使用過的內存空間一次清理掉,如下圖所示:

 

缺點:使用內存比原來縮小了一半。

現在的商業虛擬機都采用這種收集算法來回收新生代,有企業分析的得出其實並不需求將內存按1:1的比例划分,因為新生代中的對象大部分都是“朝生夕死”的。所以,HotSpot虛擬機默認的Eden和Survivor的大小比例是8:1。一塊Eden和兩塊Survivor,每次使用一塊Eden和一塊Survivor,也就是說只有10%是浪費的。如果另一塊Survivor都無法存放上次垃圾回收的對象時,那這些對象將通過“擔保機制”進入老年代了。

(3)標記-整理(Mark-Compact)算法

復制算法一般是對對象存活率較低的一種回收操作,但對於對象存活率較高的內存區域(老年代)來說,效果就不是那么理想了,標記-整理算法因此誕生了。標記-整理算法和標記-清除算法差不多,都是一開始對回收對象進行標記,但后續不是直接對對象清理,而是讓所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內存,如下圖所示:

 

(4)分代收集算法

分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根據對象存活的生命周期將內存划分為若干個不同的區域。一般情況下將堆區划分為老年代(Tenured Generation)和新生代(Young Generation),老年代的特點是每次垃圾收集時只有少量對象需要被回收,而新生代的特點是每次垃圾回收時都有大量的對象需要被回收,那么就可以根據不同代的特點采取最適合的收集算法。

1.2 垃圾收集器:

(1)七種垃圾收集器:

  1. Serial(串行GC)-復制
  2. ParNew(並行GC)-復制
  3. Parallel Scavenge(並行回收GC)-復制
  4. Serial Old(MSC)(串行GC)-標記-整理
  5. CMS(並發GC)-標記-清除
  6. Parallel Old(並行GC)--標記-整理
  7. G1(JDK1.7update14才可以正式商用)

說明:

  1. 1~3用於年輕代垃圾回收:年輕代的垃圾回收稱為minor GC
  2. 4~6用於年老代垃圾回收(當然也可以用於方法區的回收):年老代的垃圾回收稱為full GC
  3. G1獨立完成"分代垃圾回收"

注意:並行與並發

  1. 並行:多條垃圾回收線程同時操作
  2. 並發:垃圾回收線程與用戶線程一起操作

(2)常用五種組合:

  1. Serial/Serial Old
  2. ParNew/Serial Old:與上邊相比,只是比年輕代多了多線程垃圾回收而已
  3. ParNew/CMS:當下比較高效的組合
  4. Parallel Scavenge/Parallel Old:自動管理的組合
  5. G1:最先進的收集器,但是需要JDK1.7update14以上

(2.1)Serial/Serial Old:

 

特點:

  • 年輕代Serial收集器采用單個GC線程實現"復制"算法(包括掃描、復制)
  • 年老代Serial Old收集器采用單個GC線程實現"標記-整理"算法
  • Serial與Serial Old都會暫停所有用戶線程(即STW)

說明:

STW(stop the world):編譯代碼時為每一個方法注入safepoint(方法中循環結束的點、方法執行結束的點),在暫停應用時,需要等待所有的用戶線程進入safepoint,之后暫停所有線程,然后進行垃圾回收。

適用場合:

  • CPU核數<2,物理內存<2G的機器(簡單來講,單CPU,新生代空間較小且對STW時間要求不高的情況下使用)
  • -XX:UseSerialGC:強制使用該GC組合
  • -XX:PrintGCApplicationStoppedTime:查看STW時間
  • 由於它實現相對簡單,沒有線程相關的額外開銷(主要指線程切換與同步),因此非常適合運行於客戶端PC的小型應用程序,或者桌面應用程序(比如swing編寫的用戶界面程序),以及我們平時的開發、調試、測試等。

(2.2)ParNew/Serial Old:

 

說明:

ParNew除了采用多GC線程來實現復制算法以外,其他都與Serial一樣,但是此組合中的Serial Old又是一個單GC線程,所以該組合是一個比較尷尬的組合,在單CPU情況下沒有Serial/Serial Old速度快(因為ParNew多線程需要切換),在多CPU情況下又沒有之后的三種組合快(因為Serial Old是單GC線程),所以使用其實不多。

-XX:ParallelGCThreads:指定ParNew GC線程的數量,默認與CPU核數相同,該參數在於CMS GC組合時,也可能會用到

(2.3)Parallel Scavenge/Parallel Old:

 

特點:

  1. 年輕代Parallel Scavenge收集器采用多個GC線程實現"復制"算法(包括掃描、復制)
  2. 年老代Parallel Old收集器采用多個GC線程實現"標記-整理"算法
  3. Parallel Scavenge與Parallel Old都會暫停所有用戶線程(即STW)

說明:

  1. 吞吐量:CPU運行代碼時間/(CPU運行代碼時間+GC時間)
  2. CMS主要注重STW的縮短(該時間越短,用戶體驗越好,所以主要用於處理很多的交互任務的情況)
  3. Parallel Scavenge/Parallel Old主要注重吞吐量(吞吐量越大,說明CPU利用率越高,所以主要用於處理很多的CPU計算任務而用戶交互任務較少的情況)

參數設置:

  1. -XX:+UseParallelOldGC:使用該GC組合
  2. -XX:GCTimeRatio:直接設置吞吐量大小,假設設為19,則允許的最大GC時間占總時間的1/(1 +19),默認值為99,即1/(1+99)
  3. -XX:MaxGCPauseMillis:最大GC停頓時間,該參數並非越小越好
  4. -XX:+UseAdaptiveSizePolicy:開啟該參數,-Xmn/-XX:SurvivorRatio/-XX:PretenureSizeThreshold這些參數就不起作用了,虛擬機會自動收集監控信息,動態調整這些參數以提供最合適的的停頓時間或者最大的吞吐量(GC自適應調節策略),而我們需要設置的就是-Xmx,-XX:+UseParallelOldGC或-XX:GCTimeRatio兩個參數就好(當然-Xms也指定上與-Xmx相同就好)

適用場合:

  1. 很多的CPU計算任務而用戶交互任務較少的情況
  2. 不想自己去過多的關注GC參數,想讓虛擬機自己進行調優工作
  3. 對吞吐量要求較高,或需要達到一定的量。

(2.4)ParNew/CMS:

 

說明:

  1. 以上只是年老代CMS收集的過程,年輕代ParNew看"2.2、ParNew/Serial Old"就好
  2. CMS是多回收線程的,不要被上圖誤導,默認的線程數:(CPU數量+3)/4
  3. CMS主要注重STW的縮短(該時間越短,用戶體驗越好,所以主要用於處理很多的交互任務的情況)

特點:

1.年輕代ParNew收集器采用多個GC線程實現"復制"算法(包括掃描、復制)

2.年老代CMS收集器采用多線程實現"標記-清除"算法

  • 初始標記:標記與根集合節點直接關聯的節點。時間非常短,需要STW
  • 並發標記:遍歷之前標記到的關聯節點,繼續向下標記所有存活節點。時間較長。
  • 重新標記:重新遍歷trace並發期間修改過的引用關系對象。時間介於初始標記與並發標記之間,通常不會很長。需要STW
  • 並發清理:直接清除非存活對象,清理之后,將該線程占用的CPU切換給用戶線程

3.初始標記與重新標記都會暫停所有用戶線程(即STW),但是時間較短;並發標記與並發清理時間較長,但是不需要STW

關於並發標記期間怎樣記錄發生變動的引用關系對象,在重新標記期間怎樣掃描這些對象

缺點:

  • 並發標記與並發清理:按照說明的第二點來講,假設有2個CPU,那么其中有一個CPU會用於垃圾回收,而另一個用於用戶線程,這樣的話,之前是兩CPU運行用戶線程,現在是一個,那么效率就會急劇下降。也就是說,降低了吞吐量(即降低了CPU使用率)。
  • 並發清理:在這一過程中,產生的垃圾無法被清理(因為發生在重新標記之后)
  • 並發標記與並發清理:由於是與用戶線程並發的,所以用戶線程可能會分配對象,這樣既可能對象直接進入年老代(例如,大對象),也可能進入年輕代后,年輕代發生minor GC,這樣的話,實際上要求我們的年老代需要預留一定空間,也就是說要在年老代還有一定空間的情況下就要進行垃圾回收,留出一定內存空間來供其他線程使用,而不能等到年老代快爆滿了才進行垃圾回收,通過-XX:CMSInitiatingOccupancyFraction來指定當年老代空間滿了多少后進行垃圾回收
  • 標記-清理算法:會產生內存碎片,由於是在老年代,可能會提前觸發Full GC(這正是我們要盡量減少的)

參數設置:

  • -XX:+UseConcMarkSweepGC:使用該GC組合
  • -XX:CMSInitiatingOccupancyFraction:指定當年老代空間滿了多少后進行垃圾回收
  • -XX:+UseCMSCompactAtFullCollection:(默認是開啟的)在CMS收集器頂不住要進行FullGC時開啟內存碎片整理過程,該過程需要STW
  • -XX:CMSFullGCsBeforeCompaction:指定多少次FullGC后才進行整理
  • -XX:ParallelCMSThreads:指定CMS回收線程的數量,默認為:(CPU數量+3)/4

適用場合:

用於處理很多的交互任務的情況

方法區的回收一般使用CMS,配置兩個參數:-XX:+CMSPermGenSweepingEnabled與-XX:+CMSClassUnloadingEnabled

適用於一些需要長期運行且對相應時間有一定要求的后台程序

(2.5)G1

 

說明:

  • 從上圖來看,G1與CMS相比,僅在最后的"篩選回收"部分不同(CMS是並發清除),實際上G1回收器的整個堆內存的划分都與其他收集器不同。
  • CMS需要配合ParNew,G1可單獨回收整個空間

原理:

  • G1收集器將整個堆划分為多個大小相等的Region
  • G1跟蹤各個region里面的垃圾堆積的價值(回收后所獲得的空間大小以及回收所需時間長短的經驗值),在后台維護一張優先列表,每次根據允許的收集時間,優先回收價值最大的region,這種思路:在指定的時間內,掃描部分最有價值的region(而不是掃描整個堆內存),並回收,做到盡可能的在有限的時間內獲取盡可能高的收集效率。

運作流程:

  • 初始標記:標記出所有與根節點直接關聯引用對象。需要STW
  • 並發標記:遍歷之前標記到的關聯節點,繼續向下標記所有存活節點。在此期間所有變化引用關系的對象,都會被記錄在Remember Set Logs中
  • 最終標記:標記在並發標記期間,新產生的垃圾。需要STW
  • 篩選回收:根據用戶指定的期望回收時間回收價值較大的對象(看"原理"第二條)。需要STW

優點:

  1. 停頓時間可以預測:我們指定時間,在指定時間內只回收部分價值最大的空間,而CMS需要掃描整個年老代,無法預測停頓時間
  2. 無內存碎片:垃圾回收后會整合空間,CMS采用"標記-清理"算法,存在內存碎片
  3. 篩選回收階段:
  • 由於只回收部分region,所以STW時間我們可控,所以不需要與用戶線程並發爭搶CPU資源,而CMS並發清理需要占據一部分的CPU,會降低吞吐量。
  • 由於STW,所以不會產生"浮動垃圾"(即CMS在並發清理階段產生的無法回收的垃圾)

適用范圍:

  • 追求STW短:若ParNew/CMS用的挺好,就用這個;若不符合,用G1
  • 追求吞吐量:用Parallel Scavenge/Parallel Old,而G1在吞吐量方面沒有優勢


免責聲明!

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



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