JVM系列(三)垃圾收集器



垃圾回收算法是內存回收的方法論,垃圾收集器是內存回收的具體實現




HotSpot垃圾收集器


垃圾回收算法在之前已經介紹過,參見:JVM垃圾回收算法

如圖一共有七種垃圾收集器,Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1上面爲新生代收集器,下面是老年代收集器。如果兩個收集器之間存在連線,就說明它們可以搭配使用。

概念介紹:

並行(Parallel):指多條垃圾收集器線程並行工作,但此時用戶線程仍然處於等待。

併發(Concurrent):指用戶線程與垃圾收集器線程同時執行(但不一定是並行的,可能會交替執行),用戶程序在繼續運行,而垃圾收集器程序運行於另一個CPU之上。

 


七種垃圾收集器


1.Serial收集器(新生代)

        Serial收集器是一個新生代收集器,單線程執行,使用複製算法。它在進行垃圾收集時,它不僅只會使用一個CPU或者一條收集線程去完成垃圾收集作,而且必須暫停其他所有的工作線程(”Stop the world“),直到它收集完成。Serial收集器是Jvm client模式下默認的新生代收集器。

優點的:簡單而高效,對於限定單個CPU的環境來說,Serial收集器由於沒有線程交互的開銷,專心做垃圾收集自然可以獲得較高的手機效率。到目前爲止,Serial收集器依然是Client模式下的默認的新生代垃圾收集器。



2.Serial Old收集器(老年代)

        Serial Old是Serial收集器的老年代版本,它同樣使用一個單線程執行收集,使用“標記-整理”算法。主要使用在Client模式下的虛擬機。

        如果在Server模式下,那麼它主要還有兩大用途:一種用途是在JDK 1.5以及之前的版本中與Parallel Scavenge收集器搭配使用,另一種用途就是作爲CMS收集器的後備預案,在併發收集發生Concurrent Mode Failure時使用。見上圖。


3.ParNew收集器(新生代)

        ParNew收集器其實就是serial收集器的多線程版本,使用複製算法。除了使用多條線程進行垃圾收集之外,其餘行爲與Serial收集器一樣。

    ParNew/Serial Old收集器運行示意圖(表示ParNew和Serial Old搭配使用)

  是運行在Service模式下虛擬機中首選的新生代收集器,其中一個與性能無關的原因就是除了Serial收集器外,目前只有ParNew收集器能與CMS收集器配合工作。

  PreNew收集器在單CPU環境中絕對沒有Serial的效果好,由於存在線程交互的開銷,該收集器在超線程技術實現的雙CPU中都不能一定超過Serial收集器。默認開啓的垃圾收集器線程數就是CPU數量,可通過-XX:parallelGCThreads參數來限制收集器線程數。



4.ParNew收集器(新生代)    

      Parallel Scavenge收集器也是一個新生代收集器,它也是使用複製算法的收集器,又是並行多線程收集器。parallel Scavenge收集器的特點是它的關注點與其他收集器不同,CMS等收集器的關注點是儘可能地縮短垃圾收集時用戶線程的停頓時間,而parallel Scavenge收集器的目標則是達到一個可控制的吞吐量。吞吐量= 程序運行時間/(程序運行時間 + 垃圾收集時間),虛擬機總共運行了100分鐘。其中垃圾收集花掉1分鐘,那吞吐量就是99%。短停頓時間適合和用戶交互的程序,體驗好。高吞吐量適合高效利用CPU,主要用於後臺運算不需要太多交互。

      提供了兩個參數來精確控制吞吐量:1.最大垃圾收集器停頓時間(-XX:MaxGCPauseMillis    大於0的毫秒數,停頓時間小了就要犧牲相應的吞吐量和新生代空間),2.設置吞吐量大小(-XX:GCTimeRatio    大於0小於100的整數,默認99,也就是允許最大1%的垃圾回收時間)。

  還有一個參數表示自適應調節策略(GC Ergonomics)(-XX:UseAdaptiveSizePolicy)。就不用手動設置新生代大小(-Xmn)、Eden和Survivor區的比例(-XX:SurvivorRatio)今生老年代對象大小(-XX:PretenureSizeThreshold),會根據當前系統的運行情況手機監控信息,動態調整停頓時間和吞吐量大小。也是其與PreNew收集器的一個重要區別,也是其無法與CMS收集器搭配使用的原因(CMS收集器儘可能地縮短垃圾收集時用戶線程的停頓時間,以提升交互體驗)。

Parallel Scavenge/Parallel Old收集器運行示意圖



5.Parallel Old收集器(老年代)

      Parallel Old是Parallel Scavenge收集器的老年代版本,使用多線程和“標記-整理”算法,JDK1.6才提供。其通常與Parallel Scavenge收集器配合使用,“吞吐量優先”收集器是這個組合的特點,在注重吞吐量和CPU資源敏感的場合,都可以使用這個組合。見上圖。



6.CMS收集器(老年代)

CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器。CMS收集器是HotSpot虛擬機中的一款真正意義上的併發收集器,第一次實現了讓垃圾回收線程和用戶線程(基本上)同時工作。用CMS收集老年代的時候,新生代只能選擇Serial或者ParNew收集器。

CMS收集器是基於“標記-清除”算法實現的,整個收集過程大致分爲4個步驟:

①.初始標記(CMS initial mark)

②.併發標記(CMS concurrenr mark)

③.重新標記(CMS remark)

④.併發清除(CMS concurrent sweep)

其中初始標記、重新標記這兩個步驟任然需要停頓其他用戶線程(Stop The World)。初始標記僅僅只是標記出GC ROOTS能直接關聯到的對象,速度很快,併發標記階段是進行GC ROOTS 根搜索算法階段,會判定對象是否存活。而重新標記階段則是爲了修正併發標記期間,因用戶程序繼續運行而導致標記產生變動的那一部分對象的標記記錄,這個階段的停頓時間會被初始標記階段稍長,但比並發標記階段要短。

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

CMS收集器的優點:併發收集、低停頓。

但是CMS還遠遠達不到完美,器主要有三個顯著缺點:

1.CMS收集器對CPU資源非常敏感。在併發(併發標記、併發清除)階段,雖然不會導致用戶線程停頓,但是會佔用CPU資源而導致應用程序變慢,總吞吐量下降。CMS默認啓動的回收線程數是:(CPU數量+3) / 4。收集器線程所佔用的CPU數量爲:(CPU+3)/4=0.25+3/(4*CPU)。因此這時垃圾收集器始終不會佔用少於25%的CPU,因此當進行併發階段時,雖然用戶線程可以跑,但是很緩慢,特別是雙核CPU的時候,已經佔用了5/8的CPU,吞吐量會很低。爲了解決這種情況,產生了“增量式併發收集器”(Incremental Concurrent Mark Sweep/i-CMS)。就是採用搶佔方式來模擬多任務機制,就是在併發(併發標記、併發清除)階段,讓GC線程、用戶線程交替執行,儘量減少GC線程獨佔CPU,這樣垃圾收集過程更長,但是對用戶程序影響小一些。實際上i-CMS效果很一般,目前已經被聲明爲“deprecated”。

2.CMS收集器無法處理浮動垃圾,可能出現“Concurrent Mode Failure“,失敗後而導致另一次Full  GC的產生。由於CMS併發清理階段用戶線程還在運行,伴隨程序的運行自熱會有新的垃圾不斷產生,這一部分垃圾出現在標記過程之後,CMS無法在本次收集中處理它們,只好留待下一次GC時將其清理掉。這一部分垃圾稱爲“浮動垃圾”。也是由於在垃圾收集階段用戶線程還需要運行,即需要預留足夠的內存空間給用戶線程使用,因此CMS收集器不能像其他收集器那樣等到老年代幾乎完全被填滿了再進行收集,需要預留一部分內存空間提供併發收集時的程序運作使用。在默認設置下,CMS收集器在老年代使用了68%的空間時就會被激活,也可以通過參數-XX:CMSInitiatingOccupancyFraction的值來提高觸發百分比,以降低內存回收次數提高性能。JDK1.6中,CMS收集器的啓動閾值已經提升到92%。要是CMS運行期間預留的內存無法滿足程序其他線程需要,就會出現“Concurrent Mode Failure”失敗,這時候虛擬機將啓動後備預案:臨時啓用Serial Old收集器來重新進行老年代的垃圾收集,這樣停頓時間就很長了。所以說參數-XX:CMSInitiatingOccupancyFraction設置的過高將會很容易導致“Concurrent Mode Failure”失敗,性能反而降低。

3.CMS是基於“標記-清除”算法實現的收集器,使用“標記-清除”算法收集後,會產生大量碎片。空間碎片太多時,將會給對象分配帶來很多麻煩,比如說大對象,內存空間找不到連續的空間來分配不得不提前觸發一次Full  GC。爲了解決這個問題,CMS收集器提供了一個-XX:UseCMSCompactAtFullCollection開關參數,用於在Full  GC之後增加一個內存碎片的合併整理過程,但是內存整理過程是無法併發的,因此解決了空間碎片問題,卻使停頓時間變長。還可通過-XX:CMSFullGCBeforeCompaction參數設置執行多少次不壓縮的Full  GC之後,跟着來一次碎片整理過程(默認值是0,表示每次進入Full GC時都進行碎片整理)。



7.G1收集器

G1(Garbage First)收集器是JDK1.7提供的一個新的面向服務端應用的垃圾收集器,其目標就是替換掉JDK1.5發佈的CMS收集器。其優點有:

1.併發與並行:G1能充分利用多CPU、多核環境下的硬件優勢,使用多個CPU(CPU或CPU核心)來縮短停頓(Stop The World)時間。

2.分代收集:G1不需要與其他收集器配合就能獨立管理整個GC堆,但他能夠採用不同方式去處理新建對象和已經存活了一段時間、熬過多次GC的老年代對象以獲取更好收集效果。

3.空間整合:從整體來看是基於“標記-整理”算法實現,從局部(兩個Region之間)來看是基於“複製”算法實現的,但是都意味着G1運行期間不會產生內存碎片空間,更健康,遇到大對象時,不會因爲沒有連續空間而進行下一次GC,甚至一次Full GC。

4.可預測的停頓:降低停頓是G1和CMS共同關注點,但G1除了追求低停頓,還能建立可預測的停頓模型,可以明確地指定在一個長度爲M的時間片內,消耗在垃圾收集的時間不超過N毫秒

5.跨代特性:之前的收集器進行收集的範圍都是整個新生代或老年代,而G1擴展到整個Java堆(包括新生代,老年代)。

那麼是怎麼實現的呢?

1.如何實現新生代和老年代全範圍收集:其實它的Java堆佈局就不同於其餘收集器,它將整個Java堆劃分爲多個大小相等的獨立區域(Region),仍然保留新生代和老年代的概念,可是不是物理隔離的,都是一部分Region(不需要連續)的集合。

2.如何建立可預測的停頓時間模型:是因爲有了獨立區域Region的存在,就避免在Java堆中進行全區域的垃圾收集,G1跟蹤各個Region裏面的垃圾堆積的價值大小(回收可以獲得的空間大小和回收所需要的時間的經驗值),後臺維護一個優先隊列,根據每次允許的收集時間,優先回收價值最大的Region(Garbage-First理念)。因此使用Region劃分內存空間以及有優先級的區域回收方式,保證了有限時間獲得儘可能高的收集效率。

3.如何保證垃圾回收真的在Region區域進行而不會擴散到全局:由於Region並不是孤立的,一個Region的對象可以被整個Java堆的任意其餘Region的對象所引用,在做可達性判定確定對象是否存活時,仍然會關聯到Java堆的任意對象,G1中這種情況特別明顯。而以前在別的分代收集裏面,新生代規模要比老年代小許多,新生代收集也頻繁得多,也會涉及到掃描新生代時也會掃描老年代的情況,相反亦然。解決:G1收集器Region之間的對象引用以及新生代和老年代之間的對象引用,虛擬機都是使用Remembered Set來避免全堆掃描。G1中每個Region都有一個與之對應的Remembered Set,虛擬機發現程序在對Reference類型的數據進行寫操作時,會產生一個Write Barrier暫時中斷寫操作,檢查Reference引用的對象是否處於不同的Region之中(分代的例子中就檢查是否老年代對象引用了新生代的對象),如果是則通過CardTable把相關引用信息記錄到被引用對象所屬的Region的Remembered Set之中,當進行內存回收時,在GC根節點的枚舉範圍中加入Remembered Set即可避免全堆掃描。

忽略Remembered Set的維護,G1的運行步驟可簡單描述爲:

①.初始標記(Initial Marking)

②.併發標記(Concurrenr Marking)

③.最終標記(Final Marking)

④.篩選回收(Live Data Counting And Evacution)

1.初始標記:初始標記僅僅標記GC Roots能直接關聯到的對象,並且修改TAMS(Next Top at Mark Start)的值,讓下一階段用戶程序併發運行時,能在正確可用的Region中創建新的對象。這階段需要停頓線程,不可並行執行,但是時間很短。

2.併發標記:此階段是從GC Roots開始對堆中對象進行可達性分析,找出存活對象,此階段時間較長可與用戶程序併發執行。

3.最終標記:此階段是爲了修正在併發標記期間因爲用戶線程繼續運行而導致標記產生變動的那一份標記記錄,虛擬機將這段時間對象變化記錄在線程Remembered Set Logs裏面,最終標記階段需要把Remembered Set Logs的數據合併到Remembered Set中,這段時間需要停頓線程,但是可並行執行。

4.篩選回收:對各個Region的回收價值和成本進行排序,根據用戶期望的GC停頓時間來制定回收計劃。

如果現有的垃圾收集器沒有出現任何問題,沒有任何理由去選擇G1,如果應用追求低停頓,G1可選擇,如果追求吞吐量,和Parallel Scavenge/Parallel Old組合相比G1並沒有特別的優勢。



垃圾收集器參數

-XX:+<option> 啓用選項

-XX:-<option> 不啓用選項

-XX:<option>=<number> 

-XX:<option>=<string>

參數描述

-XX:+UseSerialGC

Jvm運行在Client模式下的默認值,打開此開關後,使用Serial + Serial Old的收集器組合進行內存回收
-XX:+UseParNewGC打開此開關後,使用ParNew + Serial Old的收集器進行垃圾回收
-XX:+UseConcMarkSweepGC使用ParNew + CMS +  Serial Old的收集器組合進行內存回收,Serial Old作爲CMS出現“Concurrent Mode Failure”失敗後的後備收集器使用。
-XX:+UseParallelGCJvm運行在Server模式下的默認值,打開此開關後,使用Parallel Scavenge +  Serial Old的收集器組合進行回收
-XX:+UseParallelOldGC使用Parallel Scavenge +  Parallel Old的收集器組合進行回收
-XX:SurvivorRatio新生代中Eden區域與Survivor區域的容量比值,默認爲8,代表Eden:Subrvivor = 8:1
-XX:PretenureSizeThreshold直接晉升到老年代對象的大小,設置這個參數後,大於這個參數的對象將直接在老年代分配
-XX:MaxTenuringThreshold晉升到老年代的對象年齡,每次Minor GC之後,年齡就加1,當超過這個參數的值時進入老年代
-XX:UseAdaptiveSizePolicy動態調整java堆中各個區域的大小以及進入老年代的年齡
-XX:+HandlePromotionFailure是否允許新生代收集擔保,進行一次minor gc後, 另一塊Survivor空間不足時,將直接會在老年代中保留
-XX:ParallelGCThreads設置並行GC進行內存回收的線程數
-XX:GCTimeRatioGC時間佔總時間的比列,默認值爲99,即允許1%的GC時間,僅在使用Parallel Scavenge 收集器時有效
-XX:MaxGCPauseMillis設置GC的最大停頓時間,在Parallel Scavenge 收集器下有效
-XX:CMSInitiatingOccupancyFraction設置CMS收集器在老年代空間被使用多少後出發垃圾收集,默認值爲68%,僅在CMS收集器時有效,-XX:CMSInitiatingOccupancyFraction=70
-XX:+UseCMSCompactAtFullCollection
由於CMS收集器會產生碎片,此參數設置在垃圾收集器後是否需要一次內存碎片整理過程,僅在CMS收集器時有效
-XX:+CMSFullGCBeforeCompaction
設置CMS收集器在進行若干次垃圾收集後再進行一次內存碎片整理過程,通常與UseCMSCompactAtFullCollection參數一起使用
-XX:+UseFastAccessorMethods
原始類型優化
-XX:+DisableExplicitGC
是否關閉手動System.gc
-XX:+CMSParallelRemarkEnabled
降低標記停頓
-XX:LargePageSizeInBytes
內存頁的大小不可設置過大,會影響Perm的大小,-XX:LargePageSizeInBytes=128m

 

Client、Server模式默認GC

 新生代GC方式老年代和持久GC方式

Client

Serial 串行GCSerial Old 串行GC
ServerParallel Scavenge  並行回收GCParallel Old 並行GC

Sun/Oracle JDK GC組合方式

 新生代GC方式老年代和持久GC方式

-XX:+UseSerialGC

Serial 串行GCSerial Old 串行GC
-XX:+UseParallelGCParallel Scavenge  並行回收GCSerial Old  並行GC
-XX:+UseConcMarkSweepGCParNew 並行GCCMS 併發GC 
當出現“Concurrent Mode Failure”時
採用Serial Old 串行GC
-XX:+UseParNewGCParNew 並行GCSerial Old 串行GC
-XX:+UseParallelOldGCParallel Scavenge  並行回收GCParallel Old 並行GC
-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC
Serial 串行GCCMS 併發GC 
當出現“Concurrent Mode Failure”時
採用Serial Old 串行GC



   一鍵關注公衆號:後端開發技術






垃圾回收算法是內存回收的方法論,垃圾收集器是內存回收的具體實現




HotSpot垃圾收集器


垃圾回收算法在之前已經介紹過,參見:JVM垃圾回收算法

如圖一共有七種垃圾收集器,Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1上面爲新生代收集器,下面是老年代收集器。如果兩個收集器之間存在連線,就說明它們可以搭配使用。

概念介紹:

並行(Parallel):指多條垃圾收集器線程並行工作,但此時用戶線程仍然處於等待。

併發(Concurrent):指用戶線程與垃圾收集器線程同時執行(但不一定是並行的,可能會交替執行),用戶程序在繼續運行,而垃圾收集器程序運行於另一個CPU之上。

 


七種垃圾收集器


1.Serial收集器(新生代)

        Serial收集器是一個新生代收集器,單線程執行,使用複製算法。它在進行垃圾收集時,它不僅只會使用一個CPU或者一條收集線程去完成垃圾收集作,而且必須暫停其他所有的工作線程(”Stop the world“),直到它收集完成。Serial收集器是Jvm client模式下默認的新生代收集器。

優點的:簡單而高效,對於限定單個CPU的環境來說,Serial收集器由於沒有線程交互的開銷,專心做垃圾收集自然可以獲得較高的手機效率。到目前爲止,Serial收集器依然是Client模式下的默認的新生代垃圾收集器。



2.Serial Old收集器(老年代)

        Serial Old是Serial收集器的老年代版本,它同樣使用一個單線程執行收集,使用“標記-整理”算法。主要使用在Client模式下的虛擬機。

        如果在Server模式下,那麼它主要還有兩大用途:一種用途是在JDK 1.5以及之前的版本中與Parallel Scavenge收集器搭配使用,另一種用途就是作爲CMS收集器的後備預案,在併發收集發生Concurrent Mode Failure時使用。見上圖。


3.ParNew收集器(新生代)

        ParNew收集器其實就是serial收集器的多線程版本,使用複製算法。除了使用多條線程進行垃圾收集之外,其餘行爲與Serial收集器一樣。

    ParNew/Serial Old收集器運行示意圖(表示ParNew和Serial Old搭配使用)

  是運行在Service模式下虛擬機中首選的新生代收集器,其中一個與性能無關的原因就是除了Serial收集器外,目前只有ParNew收集器能與CMS收集器配合工作。

  PreNew收集器在單CPU環境中絕對沒有Serial的效果好,由於存在線程交互的開銷,該收集器在超線程技術實現的雙CPU中都不能一定超過Serial收集器。默認開啓的垃圾收集器線程數就是CPU數量,可通過-XX:parallelGCThreads參數來限制收集器線程數。



4.ParNew收集器(新生代)    

      Parallel Scavenge收集器也是一個新生代收集器,它也是使用複製算法的收集器,又是並行多線程收集器。parallel Scavenge收集器的特點是它的關注點與其他收集器不同,CMS等收集器的關注點是儘可能地縮短垃圾收集時用戶線程的停頓時間,而parallel Scavenge收集器的目標則是達到一個可控制的吞吐量。吞吐量= 程序運行時間/(程序運行時間 + 垃圾收集時間),虛擬機總共運行了100分鐘。其中垃圾收集花掉1分鐘,那吞吐量就是99%。短停頓時間適合和用戶交互的程序,體驗好。高吞吐量適合高效利用CPU,主要用於後臺運算不需要太多交互。

      提供了兩個參數來精確控制吞吐量:1.最大垃圾收集器停頓時間(-XX:MaxGCPauseMillis    大於0的毫秒數,停頓時間小了就要犧牲相應的吞吐量和新生代空間),2.設置吞吐量大小(-XX:GCTimeRatio    大於0小於100的整數,默認99,也就是允許最大1%的垃圾回收時間)。

  還有一個參數表示自適應調節策略(GC Ergonomics)(-XX:UseAdaptiveSizePolicy)。就不用手動設置新生代大小(-Xmn)、Eden和Survivor區的比例(-XX:SurvivorRatio)今生老年代對象大小(-XX:PretenureSizeThreshold),會根據當前系統的運行情況手機監控信息,動態調整停頓時間和吞吐量大小。也是其與PreNew收集器的一個重要區別,也是其無法與CMS收集器搭配使用的原因(CMS收集器儘可能地縮短垃圾收集時用戶線程的停頓時間,以提升交互體驗)。

Parallel Scavenge/Parallel Old收集器運行示意圖



5.Parallel Old收集器(老年代)

      Parallel Old是Parallel Scavenge收集器的老年代版本,使用多線程和“標記-整理”算法,JDK1.6才提供。其通常與Parallel Scavenge收集器配合使用,“吞吐量優先”收集器是這個組合的特點,在注重吞吐量和CPU資源敏感的場合,都可以使用這個組合。見上圖。



6.CMS收集器(老年代)

CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器。CMS收集器是HotSpot虛擬機中的一款真正意義上的併發收集器,第一次實現了讓垃圾回收線程和用戶線程(基本上)同時工作。用CMS收集老年代的時候,新生代只能選擇Serial或者ParNew收集器。

CMS收集器是基於“標記-清除”算法實現的,整個收集過程大致分爲4個步驟:

①.初始標記(CMS initial mark)

②.併發標記(CMS concurrenr mark)

③.重新標記(CMS remark)

④.併發清除(CMS concurrent sweep)

其中初始標記、重新標記這兩個步驟任然需要停頓其他用戶線程(Stop The World)。初始標記僅僅只是標記出GC ROOTS能直接關聯到的對象,速度很快,併發標記階段是進行GC ROOTS 根搜索算法階段,會判定對象是否存活。而重新標記階段則是爲了修正併發標記期間,因用戶程序繼續運行而導致標記產生變動的那一部分對象的標記記錄,這個階段的停頓時間會被初始標記階段稍長,但比並發標記階段要短。

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

CMS收集器的優點:併發收集、低停頓。

但是CMS還遠遠達不到完美,器主要有三個顯著缺點:

1.CMS收集器對CPU資源非常敏感。在併發(併發標記、併發清除)階段,雖然不會導致用戶線程停頓,但是會佔用CPU資源而導致應用程序變慢,總吞吐量下降。CMS默認啓動的回收線程數是:(CPU數量+3) / 4。收集器線程所佔用的CPU數量爲:(CPU+3)/4=0.25+3/(4*CPU)。因此這時垃圾收集器始終不會佔用少於25%的CPU,因此當進行併發階段時,雖然用戶線程可以跑,但是很緩慢,特別是雙核CPU的時候,已經佔用了5/8的CPU,吞吐量會很低。爲了解決這種情況,產生了“增量式併發收集器”(Incremental Concurrent Mark Sweep/i-CMS)。就是採用搶佔方式來模擬多任務機制,就是在併發(併發標記、併發清除)階段,讓GC線程、用戶線程交替執行,儘量減少GC線程獨佔CPU,這樣垃圾收集過程更長,但是對用戶程序影響小一些。實際上i-CMS效果很一般,目前已經被聲明爲“deprecated”。

2.CMS收集器無法處理浮動垃圾,可能出現“Concurrent Mode Failure“,失敗後而導致另一次Full  GC的產生。由於CMS併發清理階段用戶線程還在運行,伴隨程序的運行自熱會有新的垃圾不斷產生,這一部分垃圾出現在標記過程之後,CMS無法在本次收集中處理它們,只好留待下一次GC時將其清理掉。這一部分垃圾稱爲“浮動垃圾”。也是由於在垃圾收集階段用戶線程還需要運行,即需要預留足夠的內存空間給用戶線程使用,因此CMS收集器不能像其他收集器那樣等到老年代幾乎完全被填滿了再進行收集,需要預留一部分內存空間提供併發收集時的程序運作使用。在默認設置下,CMS收集器在老年代使用了68%的空間時就會被激活,也可以通過參數-XX:CMSInitiatingOccupancyFraction的值來提高觸發百分比,以降低內存回收次數提高性能。JDK1.6中,CMS收集器的啓動閾值已經提升到92%。要是CMS運行期間預留的內存無法滿足程序其他線程需要,就會出現“Concurrent Mode Failure”失敗,這時候虛擬機將啓動後備預案:臨時啓用Serial Old收集器來重新進行老年代的垃圾收集,這樣停頓時間就很長了。所以說參數-XX:CMSInitiatingOccupancyFraction設置的過高將會很容易導致“Concurrent Mode Failure”失敗,性能反而降低。

3.CMS是基於“標記-清除”算法實現的收集器,使用“標記-清除”算法收集後,會產生大量碎片。空間碎片太多時,將會給對象分配帶來很多麻煩,比如說大對象,內存空間找不到連續的空間來分配不得不提前觸發一次Full  GC。爲了解決這個問題,CMS收集器提供了一個-XX:UseCMSCompactAtFullCollection開關參數,用於在Full  GC之後增加一個內存碎片的合併整理過程,但是內存整理過程是無法併發的,因此解決了空間碎片問題,卻使停頓時間變長。還可通過-XX:CMSFullGCBeforeCompaction參數設置執行多少次不壓縮的Full  GC之後,跟着來一次碎片整理過程(默認值是0,表示每次進入Full GC時都進行碎片整理)。



7.G1收集器

G1(Garbage First)收集器是JDK1.7提供的一個新的面向服務端應用的垃圾收集器,其目標就是替換掉JDK1.5發佈的CMS收集器。其優點有:

1.併發與並行:G1能充分利用多CPU、多核環境下的硬件優勢,使用多個CPU(CPU或CPU核心)來縮短停頓(Stop The World)時間。

2.分代收集:G1不需要與其他收集器配合就能獨立管理整個GC堆,但他能夠採用不同方式去處理新建對象和已經存活了一段時間、熬過多次GC的老年代對象以獲取更好收集效果。

3.空間整合:從整體來看是基於“標記-整理”算法實現,從局部(兩個Region之間)來看是基於“複製”算法實現的,但是都意味着G1運行期間不會產生內存碎片空間,更健康,遇到大對象時,不會因爲沒有連續空間而進行下一次GC,甚至一次Full GC。

4.可預測的停頓:降低停頓是G1和CMS共同關注點,但G1除了追求低停頓,還能建立可預測的停頓模型,可以明確地指定在一個長度爲M的時間片內,消耗在垃圾收集的時間不超過N毫秒

5.跨代特性:之前的收集器進行收集的範圍都是整個新生代或老年代,而G1擴展到整個Java堆(包括新生代,老年代)。

那麼是怎麼實現的呢?

1.如何實現新生代和老年代全範圍收集:其實它的Java堆佈局就不同於其餘收集器,它將整個Java堆劃分爲多個大小相等的獨立區域(Region),仍然保留新生代和老年代的概念,可是不是物理隔離的,都是一部分Region(不需要連續)的集合。

2.如何建立可預測的停頓時間模型:是因爲有了獨立區域Region的存在,就避免在Java堆中進行全區域的垃圾收集,G1跟蹤各個Region裏面的垃圾堆積的價值大小(回收可以獲得的空間大小和回收所需要的時間的經驗值),後臺維護一個優先隊列,根據每次允許的收集時間,優先回收價值最大的Region(Garbage-First理念)。因此使用Region劃分內存空間以及有優先級的區域回收方式,保證了有限時間獲得儘可能高的收集效率。

3.如何保證垃圾回收真的在Region區域進行而不會擴散到全局:由於Region並不是孤立的,一個Region的對象可以被整個Java堆的任意其餘Region的對象所引用,在做可達性判定確定對象是否存活時,仍然會關聯到Java堆的任意對象,G1中這種情況特別明顯。而以前在別的分代收集裏面,新生代規模要比老年代小許多,新生代收集也頻繁得多,也會涉及到掃描新生代時也會掃描老年代的情況,相反亦然。解決:G1收集器Region之間的對象引用以及新生代和老年代之間的對象引用,虛擬機都是使用Remembered Set來避免全堆掃描。G1中每個Region都有一個與之對應的Remembered Set,虛擬機發現程序在對Reference類型的數據進行寫操作時,會產生一個Write Barrier暫時中斷寫操作,檢查Reference引用的對象是否處於不同的Region之中(分代的例子中就檢查是否老年代對象引用了新生代的對象),如果是則通過CardTable把相關引用信息記錄到被引用對象所屬的Region的Remembered Set之中,當進行內存回收時,在GC根節點的枚舉範圍中加入Remembered Set即可避免全堆掃描。

忽略Remembered Set的維護,G1的運行步驟可簡單描述爲:

①.初始標記(Initial Marking)

②.併發標記(Concurrenr Marking)

③.最終標記(Final Marking)

④.篩選回收(Live Data Counting And Evacution)

1.初始標記:初始標記僅僅標記GC Roots能直接關聯到的對象,並且修改TAMS(Next Top at Mark Start)的值,讓下一階段用戶程序併發運行時,能在正確可用的Region中創建新的對象。這階段需要停頓線程,不可並行執行,但是時間很短。

2.併發標記:此階段是從GC Roots開始對堆中對象進行可達性分析,找出存活對象,此階段時間較長可與用戶程序併發執行。

3.最終標記:此階段是爲了修正在併發標記期間因爲用戶線程繼續運行而導致標記產生變動的那一份標記記錄,虛擬機將這段時間對象變化記錄在線程Remembered Set Logs裏面,最終標記階段需要把Remembered Set Logs的數據合併到Remembered Set中,這段時間需要停頓線程,但是可並行執行。

4.篩選回收:對各個Region的回收價值和成本進行排序,根據用戶期望的GC停頓時間來制定回收計劃。

如果現有的垃圾收集器沒有出現任何問題,沒有任何理由去選擇G1,如果應用追求低停頓,G1可選擇,如果追求吞吐量,和Parallel Scavenge/Parallel Old組合相比G1並沒有特別的優勢。



垃圾收集器參數

-XX:+<option> 啓用選項

-XX:-<option> 不啓用選項

-XX:<option>=<number> 

-XX:<option>=<string>

參數描述

-XX:+UseSerialGC

Jvm運行在Client模式下的默認值,打開此開關後,使用Serial + Serial Old的收集器組合進行內存回收
-XX:+UseParNewGC打開此開關後,使用ParNew + Serial Old的收集器進行垃圾回收
-XX:+UseConcMarkSweepGC使用ParNew + CMS +  Serial Old的收集器組合進行內存回收,Serial Old作爲CMS出現“Concurrent Mode Failure”失敗後的後備收集器使用。
-XX:+UseParallelGCJvm運行在Server模式下的默認值,打開此開關後,使用Parallel Scavenge +  Serial Old的收集器組合進行回收
-XX:+UseParallelOldGC使用Parallel Scavenge +  Parallel Old的收集器組合進行回收
-XX:SurvivorRatio新生代中Eden區域與Survivor區域的容量比值,默認爲8,代表Eden:Subrvivor = 8:1
-XX:PretenureSizeThreshold直接晉升到老年代對象的大小,設置這個參數後,大於這個參數的對象將直接在老年代分配
-XX:MaxTenuringThreshold晉升到老年代的對象年齡,每次Minor GC之後,年齡就加1,當超過這個參數的值時進入老年代
-XX:UseAdaptiveSizePolicy動態調整java堆中各個區域的大小以及進入老年代的年齡
-XX:+HandlePromotionFailure是否允許新生代收集擔保,進行一次minor gc後, 另一塊Survivor空間不足時,將直接會在老年代中保留
-XX:ParallelGCThreads設置並行GC進行內存回收的線程數
-XX:GCTimeRatioGC時間佔總時間的比列,默認值爲99,即允許1%的GC時間,僅在使用Parallel Scavenge 收集器時有效
-XX:MaxGCPauseMillis設置GC的最大停頓時間,在Parallel Scavenge 收集器下有效
-XX:CMSInitiatingOccupancyFraction設置CMS收集器在老年代空間被使用多少後出發垃圾收集,默認值爲68%,僅在CMS收集器時有效,-XX:CMSInitiatingOccupancyFraction=70
-XX:+UseCMSCompactAtFullCollection
由於CMS收集器會產生碎片,此參數設置在垃圾收集器後是否需要一次內存碎片整理過程,僅在CMS收集器時有效
-XX:+CMSFullGCBeforeCompaction
設置CMS收集器在進行若干次垃圾收集後再進行一次內存碎片整理過程,通常與UseCMSCompactAtFullCollection參數一起使用
-XX:+UseFastAccessorMethods
原始類型優化
-XX:+DisableExplicitGC
是否關閉手動System.gc
-XX:+CMSParallelRemarkEnabled
降低標記停頓
-XX:LargePageSizeInBytes
內存頁的大小不可設置過大,會影響Perm的大小,-XX:LargePageSizeInBytes=128m

 

Client、Server模式默認GC

 新生代GC方式老年代和持久GC方式

Client

Serial 串行GCSerial Old 串行GC
ServerParallel Scavenge  並行回收GCParallel Old 並行GC

Sun/Oracle JDK GC組合方式

 新生代GC方式老年代和持久GC方式

-XX:+UseSerialGC

Serial 串行GCSerial Old 串行GC
-XX:+UseParallelGCParallel Scavenge  並行回收GCSerial Old  並行GC
-XX:+UseConcMarkSweepGCParNew 並行GCCMS 併發GC 
當出現“Concurrent Mode Failure”時
採用Serial Old 串行GC
-XX:+UseParNewGCParNew 並行GCSerial Old 串行GC
-XX:+UseParallelOldGCParallel Scavenge  並行回收GCParallel Old 並行GC
-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC
Serial 串行GCCMS 併發GC 
當出現“Concurrent Mode Failure”時
採用Serial Old 串行GC












   一鍵關注公衆號:後端開發技術





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