垃圾收集器G1和CMS ,以及老年代和新生代的比例設置


 

首先

1.G1是包括年輕代和年老代的GC

2.CMS是年老代GC

3.二者在某些時候都需要FullGC(serial old GC)的輔助

 

###CMS收集器:
CMS(ConCurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器,從Mark-Sweep上可以看出,CMS收集器(以下簡稱CMS)是基於“標記-清除”算法實現的。主要應用於B/S模式的服務端(希望系統停頓時間盡可能短,尤其重視響應時間)
CMS的運作過程可以分為4個步驟

**1. 初始標記(CMS initial mark) **

**2. 並發標記(CMS concurrent mark) **

3. 重新標記(CMS remark)

4. 並發清除(CMS concurrent sweep)

  1. 詳細說明:初始標記(CMS initial Mark)是需要“Stop The World”。初始標記僅僅只是標記一下GC Roots能關聯到的對象,速度很快。

  2. 並發標記階段就是進行GC Roots Tracing 的過程。

  3. 重新標記階段:因為並發標記階段用戶程序繼續執行,導致原先標記產生變動,所以需要對原先標記的記錄進行修正。從這個作用可以看出,重新標記階段和初始標記階段同樣需要“Stop The World”。這個階段的停頓時間一般比初始標記階段稍長一點,但遠比並發標記的時間要短。

  4. 並發清除階段:從前面的語義解釋就可以看出,該階段用戶線程同垃圾清除線程同時執行(這里可以看出由於清除階段用戶線程還在運行, 自然就會產生新的垃圾(稱為“浮動垃圾”), 因為新產生的垃圾在垃圾標記階段之后,所以這部分新產生的垃圾CMS無法在本次收集過程中處理掉它們,只能留到下次GC時再清理)。
    這里寫圖片描述
    ####CMS收集器的缺點

    1. 對CPU資源很敏感:CMS默認啟動的回收線程數量是(CPU的個數+3)/4,如果CPU的個數在4個以上,
      收集器會占用不少於25%的CPU資源,CPU個數越多,收集器會占用不少於25%的CPU資源,
      CPU個數越多,CPU資源占比越小。但是當CPU不足4(例如2個)的時候,CMS收集器占用一半的運算能力去執行收集器線程。本來CPU負載就比較大,就會導致用戶程序的執行效率下降的很明顯。

    2. 無法處理浮動垃圾:由於最后的垃圾清除階段是並發進行的,伴隨着程序的運行產生的新的垃圾,在本次收集過程中無法處理掉,指的下次GC時再清理。
      而且還需要留足夠的內存空間給用戶線程使用,不能像其他收集器那樣等到老年代幾乎被填滿了再進行收集,需要預留一部分空間提供並發收集時用戶線程使用。

    3. 產生大量的空間碎片。CMS是基於“標記-清除”算法實現的收集器。因為這種算法在收集結束后有大量的空間碎片,當碎片過多時,會給大對象的內存
      分配帶來很大麻煩,往往在老年代中還有很大空間剩余,但是無法找到足夠大的連續空間來分配當前對象導致不得不提前觸發一次Full GC。

###G1(Garbage First)收集器
面向服務端應用的垃圾收集器。G1名稱由來:G1收集器,Java堆的內存布局是將整個Java堆分為多個大小相等的獨立區域(Region),也保留了新生代
和老年代的概念。但是新生代和老年代不再是物理隔離的,它們都是一部分Region的集合。G1跟蹤各個Region里面的垃圾堆積的價值大小(也就是回收獲得的空間大小以及回收需要的時間的經驗值),在后台維護一個優先列表,每次根據允許的的收集時間,優先回收價值最大的Region。

G1的運作過程大致划分為幾個步驟

1. 初始標記(Initial Marking)

2. 並發標記(Concurrent Marking)

3. 最終標記(Final Marking)

4. 篩選回收(Live Data Counting and Evacuation)

  1. 詳細說明第一步:初始標記(Initial Marking),同CMS收集器很相似,僅僅是標記一下GC Root能關聯到的對象,這階段需要停頓線程,但耗時很短。

  2. 並發標記(Concurrent Marking):是從GC Root開始對堆中對象進行可達性分析,找出存活的對象,這階段耗時比較長,但是可與用戶程序並發執行。

  3. 最終標記(Final Marking):為了修正在並發標記期間因為用戶程序執行而導致標記產生變動的那一部分的標記記錄。所以這個階段同初始標記階段一樣需要"Stop The World"。可以並行執行(即最終標記線程同時執行)。

  4. 篩選回收(Live Data Counting Evacuation):首先對各個Region的回收價值和成本進行排序,根據用戶所期望的GC停頓時間來制定回收計划。這個階段也是"Stop The World"的。(這個階段也可以做到同用戶線程一起並發執行,因為只回收一部分Region時間是用戶可控制的,停頓用戶線程可以大幅提高收集效率。所以選擇了“Stop The World”)。
    這里寫圖片描述
    ####G1收集器的特點:

    1. 並行與並發:G1能充分利用多CPU,多核的硬件優勢來縮短Stop—The—World停頓的時間,部分其它收集器原本需要停頓用戶線程執行的GC動作,
      G1依然可以通過並發的方式讓用戶線程繼續執行。

    2. 分代收集:同其它收集器一樣,分代概念依然在G1中保留。G1可以不需要其它收集器配合就能獨立管理整個GC堆,而且采用了不同的方式處理
      新創建的對象,已經存活一段時間的對象,熬過多次GC的舊對象,來獲得更好的收集結果。

    3. 空間整合:使用算法從整體上來看是基於標記——整理實現的。局部來看,是基於“復制”算法實現的。所以G1在運作期間不會產生內存空間碎片,
      收集后能提供規整的可用內存。這有利於程序長時間運行。

    4. 可預測的停頓:G1同CMS都追求低停頓時間。但是G1還能建立可預測的停頓時間模型(通過有計划的避免在整個Java堆中進行全區域的垃圾收集,
      而是將整個Java堆分為多個大小相等的獨立區域(Region),也保留了新生代和老年代的概念。但是新生代和老年代不再是物理隔離的,它們都是
      優先列表,每次根據允許的的收集時間,優先回收價值最大的Region。),能讓使用者明確指定在一個長度為M毫秒的時間片段內,消耗在垃圾收集
      的時間上不超過N毫秒,這幾乎是實時Java的垃圾收集器的特征。

 

總結

   隨着JVM的發展,ORACLE官方推出的JDK11又有了新算法ZGC,它對內存碎片的整理更加優化,回收暫停時間也更加縮短,具體細節本人還沒有深入研究,后面有機會可以寫文章專門介紹它。

最后,我把目前主流JDK使用到的JVM垃圾收集器采用的算法做下簡單總結,方便大家對比參考,

  1. 新生代垃圾收集器
  • Serial-復制算法:Serial收集器是新生代單線程收集器,優點是簡單高效,算是最基本、發展歷史最悠久的收集器。它在進行垃圾收集時,必須暫停其他所有的工作線程,直到它收集完成。
  • ParNew收集器-復制算法:ParNew收集器是新生代並行收集器,其實就是Serial收集器的多線程版本。
  • Parallel Scavenge(並行回收)-復制算法:
    Parallel Scavenge收集器是新生代並行收集器,追求高吞吐量,高效利用 CPU。該收集器的目標是達到一個可控制的吞吐量(Throughput)。所謂吞吐量就是CPU用於運行用戶代碼的時間與CPU總消耗時間的比值,即 吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間)。停頓時間越短就越適合需要與用戶交互的程序,良好的響應速度能提升用戶體驗,而高吞吐量則可用高效率地利用CPU時間,盡快完成程序的運算任務,主要適合在后台運算而不需要太多交互的任務。

 

  2. 老年代垃圾收集器

  • Serial Old-標記整理算法:Serial Old是Serial收集器的老年代版本,它同樣是一個單線程(串行)收集器,使用標記整理算法。這個收集器的主要意義也是在於給Client模式下的虛擬機使用
  • Parallel Old-標記整理算法:Parallel Old 是Parallel Scavenge收集器的老年代版本,使用多線程和“標記-整理”算法。
  • CMS:標記清除算法
  • G1:標記整理算法

 

 

CMS和G1的區別

 

CMS:以獲取最短回收停頓時間為目標的收集器,基於並發“標記清理”實現

過程:

1、初始標記:獨占PUC,僅標記GCroots能直接關聯的對象

2、並發標記:可以和用戶線程並行執行,標記所有可達對象

3、重新標記:獨占CPU(STW),對並發標記階段用戶線程運行產生的垃圾對象進行標記修正

4、並發清理:可以和用戶線程並行執行,清理垃圾

優點:

並發,低停頓

缺點:

1、對CPU非常敏感:在並發階段雖然不會導致用戶線程停頓,但是會因為占用了一部分線程使應用程序變慢

2、無法處理浮動垃圾:在最后一步並發清理過程中,用戶縣城執行也會產生垃圾,但是這部分垃圾是在標記之后,所以只有等到下一次gc的時候清理掉,這部分垃圾叫浮動垃圾

3、CMS使用“標記-清理”法會產生大量的空間碎片,當碎片過多,將會給大對象空間的分配帶來很大的麻煩,往往會出現老年代還有很大的空間但無法找到足夠大的連續空間來分配當前對象,不得不提前觸發一次FullGC,為了解決這個問題CMS提供了一個開關參數,用於在CMS頂不住,要進行FullGC時開啟內存碎片的合並整理過程,但是內存整理的過程是無法並發的,空間碎片沒有了但是停頓時間變長了

CMS 出現FullGC的原因:

1、年輕帶晉升到老年帶沒有足夠的連續空間,很有可能是內存碎片導致的

2、在並發過程中JVM覺得在並發過程結束之前堆就會滿,需要提前觸發FullGC

 

G1:是一款面向服務端應用的垃圾收集器

特點:

1、並行於並發:G1能充分利用CPU、多核環境下的硬件優勢,使用多個CPU(CPU或者CPU核心)來縮短stop-The-World停頓時間。部分其他收集器原本需要停頓Java線程執行的GC動作,G1收集器仍然可以通過並發的方式讓java程序繼續執行。

2、分代收集:分代概念在G1中依然得以保留。雖然G1可以不需要其它收集器配合就能獨立管理整個GC堆,但它能夠采用不同的方式去處理新創建的對象和已經存活了一段時間、熬過多次GC的舊對象以獲取更好的收集效果。也就是說G1可以自己管理新生代和老年代了。

3、空間整合:由於G1使用了獨立區域(Region)概念,G1從整體來看是基於“標記-整理”算法實現收集,從局部(兩個Region)上來看是基於“復制”算法實現的,但無論如何,這兩種算法都意味着G1運作期間不會產生內存空間碎片。

4、可預測的停頓:這是G1相對於CMS的另一大優勢,降低停頓時間是G1和CMS共同的關注點,但G1除了追求低停頓外,還能建立可預測的停頓時間模型,能讓使用這明確指定一個長度為M毫秒的時間片段內,消耗在垃圾收集上的時間不得超過N毫秒。

 

 

與其它收集器相比,G1變化較大的是它將整個Java堆划分為多個大小相等的獨立區域(Region),雖然還保留了新生代和來年代的概念,但新生代和老年代不再是物理隔離的了它們都是一部分Region(不需要連續)的集合。同時,為了避免全堆掃描,G1使用了Remembered Set來管理相關的對象引用信息。當進行內存回收時,在GC根節點的枚舉范圍中加入Remembered Set即可保證不對全堆掃描也不會有遺漏了。

 

如果不計算維護Remembered Set的操作,G1收集器的運作大致可划分為以下幾個步驟:

1、初始標記(Initial Making)

2、並發標記(Concurrent Marking)

3、最終標記(Final Marking)

4、篩選回收(Live Data Counting and Evacuation)

看上去跟CMS收集器的運作過程有幾分相似,不過確實也這樣。初始階段僅僅只是標記一下GC Roots能直接關聯到的對象,並且修改TAMS(Next Top Mark Start)的值,讓下一階段用戶程序並發運行時,能在正確可以用的Region中創建新對象,這個階段需要停頓線程,但耗時很短。並發標記階段是從GC Roots開始對堆中對象進行可達性分析,找出存活對象,這一階段耗時較長但能與用戶線程並發運行。而最終標記階段需要吧Remembered Set Logs的數據合並到Remembered Set中,這階段需要停頓線程,但可並行執行。最后篩選回收階段首先對各個Region的回收價值和成本進行排序,根據用戶所期望的GC停頓時間來制定回收計划,這一過程同樣是需要停頓線程的,但Sun公司透露這個階段其實也可以做到並發,但考慮到停頓線程將大幅度提高收集效率,所以選擇停頓。下圖為G1收集器運行示意圖:

 

 

JVM老年代和新生代的比例

Java 中的堆是 JVM 所管理的最大的一塊內存空間,主要用於存放各種類的實例對象。
在 Java 中,堆被划分成兩個不同的區域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被划分為三個區域:Eden、From Survivor、To Survivor。
這樣划分的目的是為了使 JVM 能夠更好的管理堆內存中的對象,包括內存的分配以及回收。
堆的內存模型大致為:

1

從圖中可以看出: 堆大小 = 新生代 + 老年代。其中,堆的大小可以通過參數 –Xms、-Xmx 來指定。
(本人使用的是 JDK1.6,以下涉及的 JVM 默認值均以該版本為准。)
默認的,新生代 ( Young ) 與老年代 ( Old ) 的比例的值為 1:2 ( 該值可以通過參數 –XX:NewRatio 來指定 ),即:新生代 ( Young ) = 1/3 的堆空間大小。老年代 ( Old ) = 2/3 的堆空間大小。其中,新生代 ( Young ) 被細分為 Eden 和 兩個 Survivor 區域,這兩個 Survivor 區域分別被命名為 from 和 to,以示區分。
默認的,Edem : from : to = 8 : 1 : 1 ( 可以通過參數 –XX:SurvivorRatio 來設定 ),即: Eden = 8/10 的新生代空間大小,from = to = 1/10 的新生代空間大小。
JVM 每次只會使用 Eden 和其中的一塊 Survivor 區域來為對象服務,所以無論什么時候,總是有一塊 Survivor 區域是空閑着的。
因此,新生代實際可用的內存空間為 9/10 ( 即90% )的新生代空間。


免責聲明!

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



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