1. 程式人生 > >垃圾收集器與內存分配策略之篇二:垃圾收集器

垃圾收集器與內存分配策略之篇二:垃圾收集器

開啟 full gc 行處理 意義 方案 發現 特征 sea 互聯網

五、垃圾收集器

如果說收集算法是內存回收的方法論,那麽垃圾收集器就是內存回收的具體實現。由於java虛擬機規範對垃圾收集器實現沒有任何的規範因此不同的廠商,不同的版本的虛擬機所提供的垃圾收集器都有可能會有很大的區別,並且一般都會提供參數供用戶根據自己的應用特點和要求組合出各個年代所使用的收集器。

虛擬機中所包含的垃圾收集器如下圖:

技術分享圖片

連線代代表他們可以組合使用。下面分別對以上垃圾收集器進行說明:

01)Serial 是歷史悠久的收集器,在垃圾回收期間或中斷用戶線程,是一個單線程的收集器,在進行垃圾收集的時候暫停其他所有的工作線程,直至他收集結束。由於它是在用戶不可見的時候暫停線程,這對許多用戶來說都是不可接受的。適合於單個CPU,單線程的情況下面,如果在桌面運行程序下面 即Client模式下面虛擬機來說是一個很好的選擇,因為停頓時間很小。是新生代收集器

Serial 收集器的運行過程

技術分享圖片

02)ParNew 收集器

Parnew收集器其實是serial收集器的多線程版本,除了可以使用多線程進行垃圾收集以外,其余行為包括Serial收集器的可用的所有控制參數。Parnew收集器的運行過程示例圖:

技術分享圖片

需要註意的是除了Serial收集器以外,只有ParNew收集器才能與CMS收集器一起工作。ParNew在單核CPU的情況下面絕對不會有比Serial收集器更好的效果,甚至由於存在線程的開銷,該收集器在通過超線程技術實現的兩個CPU的環境匯都不能百分百的超越serial收集器。當然隨著CPU數量的增加,他對於GC時系統資源的有效利用還是很有好處的。

兩種概念的解釋:

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

並行:指多條垃圾收集線程並行執行,但此時用戶線程處於等待狀態。

03) Parallel Scavenge收集器

Paraller Scavenge收集器是一個新生代收集器,他也是采用復制算法的收集器,又是並行的多線程收集器。他與ParNew收集器最大的不同是Parallel Scavenge收集器要達到一個可控的吞吐量。吞吐量= 運行用戶代碼的時間/(運行用戶代碼時間+垃圾收集器時間)。如:虛擬機共運行了100分鐘,垃圾收集用了1分鐘,那麽吞吐量是99%

。停頓時間越短,用戶的體驗就會更好。高的吞吐量可以高效率的利用CPU的時間,盡快的完成程序的運算任務,主要適合在後臺不需要交互的任務。

Parallel Scavenge 收集器提供了兩個參數用於精確的控制吞吐量,分別是最大垃圾收集停頓時間的-XX:MaxGcPauseMills 參數以及直接設置吞吐量大小的-XX:GCTimeRatio參數。MaxGcPauseMills參數是一個大於0的毫秒數,收集器將盡量地保證內存回收花費的時間不超過設定值。如果把停頓時間調小,會導致GC次數頻繁,吞吐量會下降。

GCTimeRatio參數的值應當是一個大於0且小於100的整數,也就是垃圾收集時間占總數的時間的比率,相當於是吞吐量的倒數。因此Paraller Scavenge收集器也別成為吞吐量優先的收集器。Paraller Scavenge收集器還有一個參數-XX:+UseAdapterSizePolicy值得關註,這是一個自適應策略的參數,一旦打開了以後,就不需要盡進行手動的指定新生代大小,Eden和Survivor區域的比例等細節參數了。虛擬機會根據當前的系統信息動態的調整這些參數,成為GC自適應的調節策略。

04)Serial Old收集器

Serial Old收集器是老年代版本,他同樣是一個單線程收集器,這個收集器的主要意義是主要是Client模式下面的虛擬機使用。Serial Old收集器使用的是標記整理算法。用途是:在JDK1.5的版本之前與Paraller Scavenge 收集器搭配使用,另一種用途是CMS收集器的備選方案。

05) Parallel Old 收集器是Paraller Scavenge 收集器的老年大版本,使用多線程和標記整理算法。因為新生代收集器Parallel Scavenge 收集器無法與CMS收集器一起工作,所以如果Parallel Scavenge 收集器選擇了在新生代使用,那麽老年代只能使用Parallel Scavenge收集器。Parallel Old 收集器只能和Parallel Scavenge收集器一起工作搭配。Serial和 ParNew收集器無法與Parallel Old收集器一起工作。

06)CMS 收集器

CMS 收集器是一個以獲取最短回收停頓時間為目標的收集器。目前很大一部分的java應用集中在互聯網網站或者B/S系統的服務端上,這類應用尤為重視服務的響應速度,希望停頓時間最短,已給用戶最好的體驗。CMS收集器就非常符合這類應用的需求。

CMS收集器是基於標記清除算法實現的,它的運作過程相對於前面的幾種的收集器來說更復雜一些。整個過程分為初始標記、並發標記、重新標記、並發清除。其中初始標記和重新標記這兩個步驟任然需要“stop the world“。初始標記僅僅只是標記一下GC Roots 能直接關聯到的對象,速度很快.並發標記階段就是進行GC Root Tracing的過程,而重現標記階段則是為了修正並發標記期間因用戶程序運作而導致的那一部分對象的記錄,這個階段的停頓時間一般會比初始標記的時間稍長一些,但遠比並發標記時間短。由於整個過程中耗時最長的並發標記和並發清除收集線程都是可以與用戶線程一起工作,所以,從總體來說,CMS收集器的內存回收過程是與用戶線程一起並發執行的。通過下圖可以清楚的看出CMS收集器的運行過程和需要停頓的時間:

技術分享圖片

CMS是一款優秀的收集器,它的主要優點在名字上面已經體現出來了:並發收集、低停頓,sun公司的一些官方文檔中也稱之為並發低停頓收集器。但是CMS收集器還遠達不到完美的程度,他有以下三個明顯的缺點:

1.CMS收集器對CPU資源特別敏感。其實,面向並發設計的程序都對CPU資源比較敏感。在並發階段,他雖然不會導致用戶線程的停頓,但是會為了占用了一部分線程而導致應用程序變慢,總吞吐量會降低。CMS默認啟動的回收線程數量是(CPU數量+3)/4 ,也就是當CPU在4個以上的時候,並發回收垃圾收集線程不少於25%的CPU資源,並隨著CPU的數量的增加而下降。但是當CPU不足4個的時候,比如說兩個的時候CMS收集線程對用戶的影響就會很大,如果本來負載就比較大的時候,還分出一部分去執行垃圾收集線程,就可能導致用戶線程執行變慢,這讓人很難接受。為了解決這一個問題,虛擬機提供了一種增量式並發收集器,思想是:在並發標記、清理的時候讓GC線程、用戶線程交替執行,盡量減少GC線程獨占資源的時間,這樣整個垃圾收集的過程會變得更長,但是對用戶程序的影響就會小一些,也就是速度下降沒有那麽明顯。

2.CMS收集器無法處理浮動垃圾。由於CMS並發清理階段是在和用戶線程一起執行的,伴隨著程序的運行自然就還會新的垃圾產生,這一部分垃圾在出現在標記過程之後的話,CMS無法在當次收集過程中進行處理,只好在下一次的垃圾清理的時候在進行清理,這一部分垃圾成為浮動垃圾。

3.CMS垃圾收集器是基於的標記清除算法,收集結束後會有大量的空間碎片產生。空間碎片過多的時候,將會給大對象分配帶來很大的麻煩,往往會出現老年代還有很多的剩余,但是無法找到足夠大的連續的空間來分配當前的對象,不得不提前觸發一次Full GC 。為了解決這個問題,CMS收集器提供了一個 -XX:+UseCMSCompactAtFullCollection開關參數(默認就是開啟的),用於在CMS收集器頂不住的時候,進行FullGC時候進行內存碎片的合並過程,內存整理過程是無法並發的,空間碎片問題沒有了,但是停頓時間會變得很長。為此虛擬機提供了另一個參數 -XX:CMSFullGCsBeforeCompaction,這個參數是用於設置執行多少次不壓縮FullGC,來執行一次帶壓縮的(默認為0,表示每次full Gc 都進行內存整理)

07) G1收集器

G1是一款面向服務端應用的垃圾收集器。HotSpot開發團隊賦予他的使命是(在比較長的時間)未來可替換jdk1.5的CMS收集器。與其他收集器相比,G1收集器的特點:

並行與並發:G1能充分利用多CPU、多核環境下的硬件優勢,使用多個CPU來縮短stop-the-world停頓的時間,部分其他收集器原本需要停頓java線程執行的GC動作,G1收集器仍然可以用並發的方式讓java線程繼續運行。

分代收集:與其他收集器一樣,分代概念在G1收集器中得已保留。可以采用不同的處理方式去處理新創建的對象和已經存活了一段時間的對象。

空間整合:G1收集器是基於標記整理算法實現的垃圾收集器,不會產生空間碎片。

可預測性停頓:這是G1相對於CMS的另一個優勢。G1除了追求低停頓以外,還能建立可預測的停頓時間模型,能讓使用者明確指定在一個長度為M毫秒的時間片段內,消耗在垃圾收集收集上的時間不超過N毫秒,這個幾乎是已經是實時java(RTSJ)的垃圾收集器的特征了。

在G1之前的其他收集都是在老年代和新生代之間進行來及回收的,而G1不在是這樣。使用G1收集器的話,java的堆內存布局就與其他的收集器有很大的差別,通它將整個java堆劃分為多個大小相等的獨立區域,雖然還保留著老年代和新生代的概念,但是新生代和老年代不在是物理隔離了,他們都是一部分Region集合。

G1收集器之所以能建立起來可預測的停頓時間模型,是因為它可以有計劃的避免在整個java堆中進行全區域的垃圾回收。G1跟蹤各個region裏面的垃圾堆價值大小,在後臺維護一個優先列表,每次都根據允許的收集時間,優先回收價值最大的Region。這種使用Region劃分內存空間以及有優先級的區域回收方式,保證了G1收集器在有限的時間內可以獲取盡可能高的收集效率。

在G1收集器中,Region之間的對象引用以及其他收集器的新生代與老年代之間的對象引用,虛擬機都是采用Rememberd Set 來避免全局掃描的。G1中每一個Region都有一個與之對應的Remember Set,虛擬機發現程序在對Reference 類型的數據進行寫操作的時候,會產生一個Write Barrier 暫時中斷寫操作,檢查Reference 引用的對象是否處於不同的Region中(在分代收集的時候,是檢查是否老年代中的對象引用了新生代的對象),如果是,便通過CardTable 把相關的引用信息記錄到被引用對象的所屬的Region的Remember Set之中。當進行垃圾回收的時候,把GC根節點的枚舉範圍中加入Rememeber Set即可保證不對全局掃描也不會有遺漏。

初始標記階段僅僅只是標記一下GC ROOTS能關聯的對象,並且能修改TAMS的值,讓下一階段用戶程序並發運行時,能在正確可用的Region中創建新的對象,這階段需要停頓線程,但耗時很短。並發標記階段是從GC ROOTS開始對堆中進行可達性分析研究,找出存活的對象,這階段耗時較長,但是可以與用戶線程並發執行。而最終標記階段是為了修正正在並發標記標記期間因為用戶線程繼續運作而導致標記產生變動的那一部分標記記錄,虛擬機將這段變動記錄在線程Remember Set Logs 裏面,最終標記階段需要把Remember Set Logs 的數據合並到Remember Set中,這個階段需要停頓線程,但是可以並行執行。最後篩選回收階段首先對各,個Region 的回收價值和成本進行排序,根據用戶期望的GC 停頓時間來制定回收計劃。

技術分享圖片

垃圾收集器與內存分配策略之篇二:垃圾收集器