1. 程式人生 > >CMS、G1收集器

CMS、G1收集器

[TOC] # CMS、G1收集器 ## 1、CMS收集器 CMS(Concurrent Mark Sweep) 收集器是一種以**獲取最短回收停頓時間**為目標的收集器。優點是**併發收集、低停頓**。目前很大一部分的Java 應用集中在網際網路站或者B/S系統的服務端上,這類應用尤其重視服務的響應速度,希望系統停頓時間最短,以給使用者帶來較好的體驗。CMS收集器就非常符合這類應用的需求。 ### 1.1、原理 CMS收集器基於“**標記—清除**”演算法實現,整個過程分為四個步驟: 1. **初始標記**(initial mark) 2. **併發標記**(concurrent mark) 3. **重新標記**(remark) 4. **併發清除**(concurrent sweep) ![](https://images.cnblogs.com/cnblogs_com/refeng/1909700/o_201231145708u=2304504768,1053283755&fm=26&gp=0.jpg) 其中,初始標記、重新標記這兩個步驟仍然需要Stop-The-World。初始標記僅僅只是標記一下GC Roots 能直接關聯到的物件,速度很快,併發標記階段就是進行Gc Roots Tracing的過程。而重新標記階段則是為了修正併發標記期間因使用者程式繼續運作而導致標記產生變動的那一部分物件的標記記錄。由於整個過程中耗時最長的併發標記和併發清除過程收集器執行緒都可以與使用者執行緒一起工作,所以,從總體上來說,CMS 收集器的記憶體回收過程是與使用者執行緒一起併發執行的。 ### 1.2、不足 (1)對CPU 資源非常敏感。 其實, 面向併發設計的程式都對 CPU 資源比較敏感。在併發階段,它雖然不會導致使用者執行緒停頓, 但是會因為佔用了一部分執行緒(或者說 CPU 資源)而導致應用程式變慢,總吞吐量會降低。CMS 預設啟動的回收執行緒數是 (CPU 數量+3) /4, 也就是當 CPU 在 4 個以上時, 併發回收時垃圾收集執行緒不少於25% 的CPU資源,並且隨著 CPU 數量的增加而下降。但是當 CPU 不足4個(比如 2 個)時,CMS 對使用者程式的影響就可能變得很大,如果本來CPU負載就比較大,還分出一半的運算能力去執行收集器執行緒,就可能導致使用者程式的執行速度忽然降低了 50%,讓人無法接受。 (2)無法處理浮動垃圾(Floating Garbage),可能出現“Concurrent Mode Failure”失敗而導致另一次Full GC的產生。 由於CMS併發清理階段使用者執行緒還在執行著,伴隨程式執行自然就還會有新的垃圾不斷產生,這一部分垃圾出現在標記過程之後,CMS無法在當次收集中處理掉它們,只好留待下一次GC時再清理掉。這一部分垃圾就稱為“**浮動垃圾**”。也是由於在垃圾收集階段使用者執行緒還需要執行,那也就還需要預留有足夠的記憶體空間給使用者執行緒使用,因此CMS收集器不能像其他收集器那樣等到老年代幾乎完全被填滿了再進行收集,需要預留一部分空間提供併發收集時的程式運作使用。 在JDK1.5的預設設定下,CMS收集器當老年代使用了68%的空間後就會被啟用,這是一個偏保守的設定,如果在應用中老年代增長不是太快, 可以適當調高參數-XX:CMSInitiatingOccupancyFraction的值來提高觸發百分比,以便降低記憶體回收次數從而獲取更好的效能,在JDK1.6中,CMS收集器的啟動閾值已經提升至92%。要是CMS執行期間預留的記憶體無法滿足程式需要,就會出現一次 "Concurrent Mode Failure"失敗,這時虛擬機器將啟動後備預案:臨時啟用SerialOld 收集器來重新進行老年代的垃圾收集,這樣停頓時間就很長了。所以說引數-XX:CMSlnitiatingOccupancyFraction設定得太高很容易導致大量"Concurrent Mode Failure" 失敗,效能反而降低。 (3)空間碎片的產生。 因為是採用”標記—清除”演算法,意味著收集結束時會有大量空間碎片產生。空間碎片過多時,將會給大物件分配帶來很大麻煩,往往會出現老年代還有很大空間剩餘,但是無法找到足夠大的連續空間來分配當前物件,不得不提前觸發一次Full GC。 為了解決這個問題,CMS收集器提供了一個-XX:+UseCMSCompactAtFullCollection開關引數(預設就是開啟的),用於在CMS收集器頂不住要進行Full GC時開啟記憶體碎片的合併整理過程,記憶體整理的過程是無法併發 的,空間碎片問題沒有了,但停頓時間不得不變長。虛擬機器設計者還提供了另外一個引數-XX:CMSFullGCsBeforeCompaction,這個引數是用於設定執行多少次不壓縮的 Full GC後,跟著來一次帶壓縮的(預設值為0,表示每次進人Full GC時都進行碎片整理)。 ## 2、G1收集器 ### 2.1、特點 G1(Garbage-Firsts,**優先處理價值大的記憶體塊**)收集器是當今收集器技術發展的最前沿成果之一,並且還在不斷髮展、完善,與其他GC收集器相比G1具備如下特點: - 並行與併發:G1能充分利用多CPU、多核環境下的硬體優勢,使用多個CPU(CPU 或者CPU核心)來縮短Stop-The-World停頓的時問,部分其他收集器原本需要停頓 Java執行緒執行的GC動作,G1收集器仍然可以通過併發的方式讓JAVA程式繼續執行 - 分代收集:與其他收集器一樣,分代概念在G1中依然得以保留。雖然G1可以不需要其他收集器配合就能獨立管理整個GC堆,但它能夠採用不同的方式去處理新建立的物件和已經存活了一段時間、熬過多次GC的舊物件以獲取更好的收集效果。 - 空間整合:與CMS的“標記一清理”演算法不同,G1從整體來看是基於“標記—整理”演算法實現的收集器,從區域性(兩個Region之間)上來看是基於“複製”演算法實現的,但無論如何,這兩種演算法都意味著G1運作期間不會產生記憶體空間碎片,收集後能提供規整的可用記憶體。這種特性有利於程式長時間執行,分配大物件時不會因為無法找到連續記憶體空間而提前觸發下一次GC。 - 可預測的停頓:這是G1相對於CMS的另一大優勢,降低停頓時間是G1和CMS共同的關注點,但G1除了追求低停頓外,還能建立**可預測的停頓時間模型**,能讓使用者明確指定在一個長度為M亳秒的時間片段內,消耗在垃圾收集上的時間不得超過N亳秒,這幾乎已經是實時Java(RTSJ)的垃圾收集器的特徵了。 在G1之前的其他收集器進行收集的範圍都是整個新生代或者老年代,而G1不再是這樣。使用G1收集器時,Java堆的記憶體佈局就與其他收集器有很大差別,它將整個Java堆劃分為**多個大小相等的獨立區域(Region)**,雖然還保留有新生代和老年代的概念,但新生代和老年代不再是物理隔離的了,它們都是一部分Region(不需要連續)的集合。 G1收集器之所以能建立可預測的停頓時間模型,是因為它可以有計劃地避免在整個Java堆中進行全區域的垃圾收集。G1 跟蹤各個Region裡面的垃圾堆積的價值大小(回收所獲得的空間大小以及回收所需時間的經驗值),在後臺維護一個優先列表,每次根據允許的收集時間,優先回收價值最大的Region (這也就是 Garbage-First 名稱的來由)。這種使用Region劃分記憶體空間以及有優先順序的區域回收方式,使得G1收集器在有限的時間內可以獲取儘可能高的收集效率。 ### 2.2、執行過程 G1收集器的運作大致可劃分為以下幾個步驟: - 初始標記 (Initial Marking) - 併發標記 (Concurrent Marking) - 最終標記 (Final Marking) - 篩選回收 (Live Data Counting and Evacuation) ![](https://images.cnblogs.com/cnblogs_com/refeng/1909700/o_201231142817u=2915340601,3458053987&fm=26&gp=0.jpg) 初始標記階段僅僅只是標記一下GC Roots能直接關聯到的物件,並且修改NTAMS(Next Top at Mark Start)的值,讓下一階段使用者程式併發執行時,能在正確可用的Region中建立新物件,這一階段需要停頓執行緒,但耗時很短。併發標記階段是從GC Roots開始對堆中的物件進行可達性分析,找出存活的物件,這一階段耗時較長,但可與使用者程式併發執行。而最終標記階段是為了修正在併發標記期間因使用者程式繼續執行而導致標記產生變動的那一部分標記記錄,這一階段需要停頓執行緒,但是可並行執行。最後在篩選回收階段首先對各個Region的回收價值和成本進行排序,根據使用者所期望的GC停頓時間來制定回收計劃。 ------ 如果有學到東西,請**點贊**給予鼓勵,