垃圾收集器與內存分配策略(二)

前言:

續更《深入理解jvm》,本來昨晚就可以出來的這篇,偷懶了=。=,keep going!!!

垃圾收集器

垃圾回收器是內存回收的具體實現。

img

兩個收集器間有連線,表明它們可以搭配使用:

  • Serial/Serial Old
  • Serial/CMS
  • ParNew/Serial Old
  • ParNew/CMS
  • Parallel Scavenge/Serial Old
  • Parallel Scavenge/Parallel Old
  • G1

其中Serial Old作爲CMS出現"Concurrent Mode Failure"失敗的後備預案

  • 新生代:

    • Serial收集器

      針對新生代;

      採用複製算法;

      單線程收集;

      進行垃圾收集時,必須暫停所有工作線程,直到完成;

      即會"Stop The World";

      對於運行在client模式下的虛擬機來說是一個不錯的選測

    • ParNew收集器

      Serial收集器的多線程版本;

      採用複製算法;

      在Server模式下,ParNew收集器是一個非常重要的收集器,因爲除Serial外,目前只有它能與CMS收集器配合工作;

      但在單個CPU環境中,不會比Serail收集器有更好的效果,因爲存在線程交互開銷。

    • Parallel Scavenge收集器

      採用複製算法;

      多線程收集;

      主要特點是:該收集器的主要目標是達到一個可控制的吞吐量,即用戶代碼時間/用戶代碼時間+垃圾收集時間

      高吞吐量爲目標,即減少垃圾收集時間,讓用戶代碼獲得更長的運行時間;

      當應用程序運行在具有多個CPU上,對暫停時間沒有特別高的要求時,即程序主要在後臺進行計算,而不需要與用戶進行太多交互,用此收集器;

      提供參數控制最大停頓時間以及吞吐量大小。

      提供 自動設置新生代內存結構分佈的參數 “-XX:+UseAdptiveSizePolicy”,設置該參數後,JVM會根據當前系統運行情況收集性能監控信息,動態調整這些參數,以提供最合適的停頓時間或最大的吞吐量,這種調節方式稱爲GC自適應的調節策略,這是它與ParNew的一個重要區別

  • 老年代

    • Serial Old收集器

      是Serial的老年代版本;

      單線程;

      標記-整理算法;

      主要用於搭配Parallel Scavenge收集器,但也會拖累起速度

    • Parallel Old

      針對老年代;

      採用"標記-整理"算法;

      多線程收集;

      可以搭配Parallel Scavenge收集器,在Server模式,多CPU的情況下,注重吞吐量以及CPU資源敏感的場景十分有用。

    • CMS收集器

      針對老年代;

      基於"標記-清除"算法(不進行壓縮操作,產生內存碎片);

      以獲取最短回收停頓時間爲目標;

      併發收集、低停頓;

      如常見WEB、B/S系統的服務器上的應用;

      他的運作流程分爲4個步驟:初始標記、併發標記、重新標記、併發清楚。其中初始標記和重新標記需要gc停頓。初始標記姿勢標記一下GC Roots能關聯到的對象,併發標記就是進行GC Roots 跟蹤的過程,重新標記則是爲了修改併發標記期間因用戶程序繼續運行而導致標記產生變動的那部分對象標記記錄。耗時最長的還是併發標記和併發清除。

      缺點:

      1.無法處理浮動垃圾:浮動垃圾(Floating Garbage)在併發清除時,用戶線程新產生的垃圾(新的無用常量、無用類)稱爲浮動垃圾;這使得併發清除時需要預留一定的老年代內存空間,不能像其他收集器在老年代幾乎填滿再進行收集,即預留空間就開始執行清除,通常68%就會執行老年代回收;也要可以認爲CMS所需要的空間比其他垃圾收集器大;Concurrent Mode Failure"失敗如果CMS預留內存空間無法滿足程序需要,就會出現一次"Concurrent Mode Failure"失敗;這時JVM啓用後備預案:臨時啓用Serail Old收集器,而導致另一次Full GC的產生;

      2.對於cpu資源敏感:併發收集雖然不會暫停用戶線程,但因爲佔用一部分CPU資源,還是會導致應用程序變慢,總吞吐量降低。 CMS的默認收集線程數量是=(CPU數量+3)/4;

      3.基於標記-清除算法實現的收集器:產生大量不連續的內存碎片會導致分配大內存對象時,無法找到足夠的連續內存,從而需要提前觸發另一次Full GC動作。

      解決方法:

      (1)、"-XX:+UseCMSCompactAtFullCollection"

      使得CMS出現上面這種情況時不進行Full GC,而開啓內存碎片的合併整理過程;

      但合併整理過程無法併發,停頓時間會變長;

      默認開啓(但不會進行,結合下面的CMSFullGCsBeforeCompaction);

      (2)、"-XX:+CMSFullGCsBeforeCompaction"

      設置執行多少次不壓縮的Full GC後,來一次壓縮整理;

      爲減少合併整理過程的停頓時間;

      默認爲0,也就是說每次都執行Full GC,不會進行壓縮整理;

  • 整個堆

    • G1收集器

      1.並行與併發

      能充分利用多CPU、多核環境下的硬件優勢;

      可以並行來縮短"Stop The World"停頓時間;

      也可以併發讓垃圾收集與用戶程序同時進行;

    ​ 2.分代收集,收集範圍包括新生代和老年代

    能獨立管理整個GC堆(新生代和老年代),而不需要與其他收集器搭配;

    能夠採用不同方式處理不同時期的對象;

    雖然保留分代概念,但Java堆的內存佈局有很大差別;

    將整個堆劃分爲多個大小相等的獨立區域(Region);

    新生代和老年代不再是物理隔離,它們都是一部分Region(不需要連續)的集合;

    ​ 3.結合多種垃圾收集算法,空間整合,不產生碎片

    從整體看,是基於標記-整理算法;

    從局部(兩個Region間)看,是基於複製算法;

內存分配與回收策略

對象的內存分配,就是在堆上分配,對象主要分配在新生代的Eden區上,少數情況直接分配到老年代中。,如果 (-XX:UseTLAB)啓動了本地線程分配緩存,則按照線程優先在TLAB()上分配。

TLAB:是虛擬機在堆內存的eden劃分出來的一塊專用空間線程專屬。在虛擬機的TLAB功能啓動的情況下,在線程初始化時,虛擬機會爲每個線程分配一塊TLAB空間,只給當前線程使用,這樣每個線程都單獨擁有一個空間,如需要分配內存,在自己的空間上分配,在不存在競爭的情況大大提升分配效率

內存分配策略:

對象優先在Eden分配

大多數情況下,對象在新生代Eden中分配,當Eden沒有足夠的空間進行分配時,虛擬機就會發起一次Minor GC。

大對象直接進入老年代

需要大量連續空間的java對象,典型的就是很長的字符串和數組,直接在老年代分配。

長期存活的對象將進入老年代

虛擬機給每個對象定義了一個對象年齡的計數器,放在對象頭中,如果對象在Eden出生並且經過了一次Minor GC後仍然存活,且存入Survivot to中,此時如果是第一次,對象的年齡會設置爲1,對象在Survivor區每度過一次Minor GC,年齡+1,默認到15,就會升爲老年代,存入老年代區域。(Eden中的對象沒有年齡,只有Survivor中的對象有年齡)

動態對象年齡判

如果Survivot空間中相同年齡的所有對象大小的和大於Survivor的一半,年齡大於活着等於該年齡的對象就可以直接進入老年代。無需等age到15;

空間分配擔保

在發生Minor GC之前,虛擬機會先檢查老年代最大可用的連續空間是否大於新生代中所有對象的總空間,如果這個條件成立Minor GC是安全的,如果不成立,則虛擬機會查看HandlerPromotionFailure設置值是否允許擔保失敗。如果允許就會檢查老年代最大可用連續空間是否大於歷次晉升老年代對象的平均大小,如果大於,那麼冒着風險嘗試着進行Minor GC,如果小於活HandlerPromotionFailure設置爲false就進行一次Full GC。

那麼爲什麼要檢查這麼多東西呢???

檢查老年代最大可用的連續空間是否大於新生代中所有對象的總空間,由於新生代採用複製算法,把Eden和Survivot from兩個區域的存活對象轉移到Survivor to,如果Survivor to就會觸發空間分配擔保,把存活對象直接放進老年代,由於無法知道存活的對象有多少,所以如果老年代可用內存大於新生代中所有對象的大小,那麼無論如何,發生空間分配擔保老年代都可以裝下來,

檢查老年代最大可用連續空間是否大於歷次晉升老年代對象的平均大小?

這是比較冒風險的一種做法,如果老年代最大可用連續空間大於歷代新生代對象晉升老年代的平均值的話,大概率是不會出現老年代空間不夠的,所以值得冒險。

如果失敗的話就會導致擔保失敗HandlerPromotionFailure,只好重新發起一次Full GC了。

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