四、GC收集器類型

一、GC收集器的分類:

按線程分為串行收集器和并行收集器,串行使用一個線程進行回收操作,并行使用多個線程進行回收,可以減少GC的停頓時間。

按工作模式分為并發(fā)式垃圾回收器和獨占式垃圾回收器,并發(fā)式回收器與應用程序線程交替工作,減少應用程序的停頓時間;獨占式回收器(stop the world )一旦運行,將阻塞應用程序線程的運行,直到回收結束。

按碎片處理方式分為壓縮式回收器和非壓縮式回收器,壓縮式回收器會進行標記-壓縮過程,消除內存碎片,而非壓縮式則相反。

按工作的內存分為新生代垃圾回收器和老年代回收器,分別工作在新生代和老年代


GC收集器分類

二、評價GC分類的標準

1.吞吐量:應用程序耗時/(應用程序耗時+GC耗時),比如系統運行耗時100min,其中GC耗時1min,則吞吐量為 (100-1)/100=99%

2.垃圾回收器負載:與吞吐量相反,是GC停頓耗時/系統運行耗時= 1/100=1%

3.停頓時間:垃圾回收期間,應用程序的停頓時間。獨占回收器的停頓時間較長,并發(fā)回收器的停頓時間較短,但是效率不如獨占回收器,吞吐量沒有其高

4.垃圾回收頻率:多少時間進行一次垃圾回收,雖然可以增加堆內存來降低回收頻率,但是會增加系統停頓時間

5.反應時間:一個對象成為垃圾后,多少時間內,所占用的內存被釋放

三、幾種收集器

????????單線程GC收集器包括Serial和SerialOld這兩款收集器,分別用于年輕代和老年代的垃圾收集工作。后來,隨著CPU多核的普及,為了更好了利用多核的優(yōu)勢,開發(fā)了ParNew收集器,這款收集器是Serial收集器的多線程版本。

  多線程收集器還包括Parallel Scavenge和ParallelOld收集器,這兩款也分別用于年輕代和老年代的垃圾收集工作,不同的是,它們是兩款可以利用多核優(yōu)勢的多線程收集器。

  相對來說更加復雜的還有CMS收集器。這款收集器,在運行的時候會分多個階段進行垃圾收集,而且在一些階段是可以和應用線程并行運行的,提高了這款收集器的收集效率。

  其中最先進的收集器,要數G1這款收集器了。這款收集器是當前最新發(fā)布的收集器,是一款面向服務端垃圾收集器。

收集器分類

1.年輕代收集器

年輕代收集器包括Serial收集器、ParNew收集器以及Parallel Scavenge收集器。

1.1Serial收集器

Serial收集器是一款年輕代的垃圾收集器,使用標記-復制垃圾收集算法。它是一款發(fā)展歷史最悠久的垃圾收集器。Serial收集器只能使用一條線程進行垃圾收集工作,并且在進行垃圾收集的時候,所有的工作線程都需要停止工作,等待垃圾收集線程完成以后,其他線程才可以繼續(xù)工作。工作過程可以簡單的用下圖來表示:

Serial收集器

?  從圖中可以看到,Serial收集器工作的時候,其他用戶線程都停止下來,等到GC過程結束以后,它們才繼續(xù)執(zhí)行。而且處理GC過程的只有一條線程在執(zhí)行。由于Serial收集器的這種工作機制,所以在進行垃圾收集過程中,會出現STW(Stop The World)的情況,應用程序會出現停頓的狀況。如果垃圾收集的時間很長,那么停頓時間也會很長,這樣會導致系統響應變的遲鈍,影響系統的時候。

  雖然這款年邁的垃圾收集器只能使用單核CPU,但是正是由于它不能利用多核,在一些場景下,減少了很多線程的上下文切換的開銷,可以在進行垃圾收集過程中專心處理GC過程,而不會被打斷,所以如果GC過程很短暫,那么這款收集器還是非常簡單高效的。

  由于Serial收集器只能使用單核CPU,在現代處理器基本都是多核多線程的情況下,為了充分利用多核的優(yōu)勢,出現了多線程版本的垃圾收集器,比如下面將要說到的ParNew收集器。

1.2ParNew收集器

  ParNew垃圾收集器是Serial收集器的多線程版本,使用標記-復制垃圾收集算法。為了利用CPU多核多線程的優(yōu)勢,ParNew收集器可以運行多個收集線程來進行垃圾收集工作。這樣可以提高垃圾收集過程的效率。

和上面的Serial收集器比較,可以明顯看到,在垃圾收集過程中,GC線程是多線程執(zhí)行的,而在Serial收集器中,只有一個GC線程在處理垃圾收集過程。ParNew收集器在很多時候都是作為服務端的年輕代收集器的選擇,除了它具有比Serial收集器更好的性能外,還有一個原因是,多線程版本的年輕代收集器中,只有它可以和CMS這款優(yōu)秀的老年代收集器一起搭配搭配使用。

  作為一款多線程收集器,當它運行在單CPU的機器上的時候,由于不能利用多核的優(yōu)勢,在線程收集過程中可能會出現頻繁上下文切換,導致額外的開銷,所以在單CPU的機器上,ParNew收集器的性能不一定好于Serial這款單線程收集器。如果機器是多CPU的,那么ParNew還是可以很好的提高GC收集的效率的。

ParNew收集器默認開啟的垃圾收集線程數是和當前機器的CPU數量相同的,為了控制GC收集線程的數量,可以通過參數-XX:ParallelGCThreads來控制垃圾收集線程的數量。

1.3Parallel Scavenge收集器

  Parallel Scavenge收集器是是一款年輕代的收集器,它使用標記-復制垃圾收集算法。和ParNew一樣,它也會一款多線程的垃圾收集器,但是它又和ParNew有很大的不同點。

  Parallel Scavenge收集器和其他收集器的關注點不同。其他收集器,比如ParNew和CMS這些收集器,它們主要關注的是如何縮短垃圾收集的時間。而Parallel Scavenge收集器關注的是如何控制系統運行的吞吐量。這里說的吞吐量,指的是CPU用于運行應用程序的時間和CPU總時間的占比,吞吐量 = 代碼運行時間 / (代碼運行時間 + 垃圾收集時間)。如果虛擬機運行的總的CPU時間是100分鐘,而用于執(zhí)行垃圾收集的時間為1分鐘,那么吞吐量就是99%。

  直觀上,好像以縮短垃圾收集的停頓時間為目的和以控制吞吐量為目的差不多,但是適用的場景卻不同。對于那些桌面應用程序,為了得到良好的用戶體驗,在交互過程中,需要得到快速的響應,所以系統的停頓時間要盡可能的快以避免影響到系統的響應速度,只要保證每次停頓的時間很短暫,假設每次停頓時間為10ms,那么即使發(fā)生很多次的垃圾收集過程,假設1000次,也不會影響到系統的響應速度,不會影響到用戶的體驗。對于一些后臺計算任務,它不需要和用戶進行交互,所以短暫的停頓時間對它而言并不需要,對于計算任務而言,更好的利用CPU時間,提高計算效率才是需要的,所以假設每次停頓時間相對很長,有100ms,而由于花費了很長的時間進行垃圾收集,那么垃圾收集的次數就會降下來,假設只有5次,那么顯然,使用以吞吐量為目的的垃圾收集器,可以更加有效的利用CPU來完成計算任務。所以,在用戶界面程序中,使用低延遲的垃圾收集器會有很好的效果,而對于后臺計算任務的系統,高吞吐量的收集器才是首選。

Parallel Scavenge收集器提供了兩個參數用于控制吞吐量。-XX:MaxGCPauseMillis用于控制最大垃圾收集停頓時間,-XX:GCTimeRatio用于直接控制吞吐量的大小。MaxGCPauseMillis參數的值允許是一個大于0的整數,表示毫秒數,收集器會盡可能的保證每次垃圾收集耗費的時間不超過這個設定值。但是如果這個這個值設定的過小,那么Parallel Scavenge收集器為了保證每次垃圾收集的時間不超過這個限定值,會導致垃圾收集的次數增加和增加年輕代的空間大小,垃圾收集的吞吐量也會隨之下降。GCTimeRatio這個參數的值應該是一個0-100之間的整數,表示應用程序運行時間和垃圾收集時間的比值。如果把值設置為19,即系統運行時間 : GC收集時間 = 19 : 1,那么GC收集時間就占用了總時間的5%(1 / (19 + 1) = 5%),該參數的默認值為99,即最大允許1%(1 / (1 + 99) = 1%)的垃圾收集時間。

Parallel Scavenge收集器還有一個參數:-XX:UseAdaptiveSizePolicy。這是一個開關參數,當開啟這個參數以后,就不需要手動指定新生代的內存大小(-Xmn)、Eden區(qū)和Survivor區(qū)的比值(-XX:SurvivorRatio)以及晉升到老年代的對象的大小(-XX:PretenureSizeThreshold)等參數了,虛擬機會根據當前系統的運行情況動態(tài)調整合適的設置值來達到合適的停頓時間和合適的吞吐量,這種方式稱為GC自適應調節(jié)策略。

  Parallel Scavenge收集器也是一款多線程收集器,但是由于目的是為了控制系統的吞吐量,所以這款收集器也被稱為吞吐量優(yōu)先收集器。

2.?老年代收集器

老年代收集包括:Serial Old收集器、Parallel Old收集器以及CMS收集器。

2.1Serial Old收集器

是Serial收集器的老年代版本,它也是一款使用"標記-整理"算法的單線程的垃圾收集器。這款收集器主要用于客戶端應用程序中作為老年代的垃圾收集器,也可以作為服務端應用程序的垃圾收集器,當它用于服務端應用系統中的時候,主要是在JDK1.5版本之前和Parallel Scavenge年輕代收集器配合使用,或者作為CMS收集器的后備收集器。

SerialOld

2.2Parallel Old收集器

  Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用"標記-整理"算法。這個收集器是在JDK1.6版本中出現的,所以在JDK1.6之前,新生代的Parallel Scavenge只能和Serial Old這款單線程的老年代收集器配合使用。Parallel Old垃圾收集器和Parallel Scavenge收集器一樣,也是一款關注吞吐量的垃圾收集器,和Parallel Scavenge收集器一起配合,可以實現對Java堆內存的吞吐量優(yōu)先的垃圾收集策略。

  Parallel Old垃圾收集器的工作原理和Parallel Scavenge收集器類似。

老年代并行回收

2.3CMS收集器

  CMS收集器是目前老年代收集器中比較優(yōu)秀的垃圾收集器。CMS是Concurrent Mark Sweep,從名字可以看出,這是一款使用"標記-清除"算法的并發(fā)收集器。CMS垃圾收集器是一款以獲取最短停頓時間為目標的收集器。由于現代互聯網中的應用,比較重視服務的響應速度和系統的停頓時間,所以CMS收集器非常適合在這種場景下使用。CMS收集器的運行過程相對上面提到的幾款收集器要復雜一些。

從圖中可以看出,CMS收集器的工作過程可以分為4個階段:

初始標記(CMS initial mark)階段

并發(fā)標記(CMS concurrent mark)階段

重新標記(CMS remark)階段

并發(fā)清除(CMS concurrent sweep)階段

  從圖中可以看出,在這4個階段中,初始標記和重新標記這兩個階段都是只有GC線程在運行,用戶線程會被停止,所以這兩個階段會發(fā)送STW(Stop The World)。初始標記階段的工作是標記GC Roots可以直接關聯到的對象,速度很快。并發(fā)標記階段,會從GC Roots 出發(fā),標記處所有可達的對象,這個過程可能會花費相對比較長的時間,但是由于在這個階段,GC線程和用戶線程是可以一起運行的,所以即使標記過程比較耗時,也不會影響到系統的運行。重新標記階段,是對并發(fā)標記期間因用戶程序運行而導致標記變動的那部分記錄進行修正,重新標記階段耗時一般比初始標記稍長,但是遠小于并發(fā)標記階段。最終,會進行并發(fā)清理階段,和并發(fā)標記階段類似,并發(fā)清理階段不會停止系統的運行,所以即使相對耗時,也不會對系統運行產生大的影響。

  由于并發(fā)標記和并發(fā)清理階段是和應用系統一起執(zhí)行的,而初始標記和重新標記相對來說耗時很短,所以可以認為CMS收集器在運行過程中,是和應用程序是并發(fā)執(zhí)行的。由于CMS收集器是一款并發(fā)收集和低停頓的垃圾收集器,所以CMS收集器也被稱為并發(fā)低停頓收集器。

  雖然CMS收集器可以是實現低延遲并發(fā)收集,但是也存在一些不足。

  首先,CMS收集器對CPU資源非常敏感。對于并發(fā)實現的收集器而言,雖然可以利用多核優(yōu)勢提高垃圾收集的效率,但是由于收集器在運行過程中會占用一部分的線程,這些線程會占用CPU資源,所以會影響到應用系統的運行,會導致系統總的吞吐量降低。CMS默認開始的回收線程數是(Ncpu + 3) / 4,其中Ncpu是機器的CPU數。所以,當機器的CPU數量為4個以上的時候,垃圾回收線程將占用不少于%25的CPU資源,并且隨著CPU數量的增加,垃圾回收線程占用的CPU資源會減少。但是,當CPU資源少于4個的時候,垃圾回收線程占用的CPU資源的比例會增大,會影響到系統的運行,假設有2個CPU的情況下,垃圾回收線程將會占據超過50%的CPU資源。所以,在選用CMS收集器的時候,需要考慮,當前的應用系統,是否對CPU資源敏感。

其次,CMS收集器在處理垃圾收集的過程中,可能會產生浮動垃圾,由于它無法處理浮動垃圾,所以可能會出現Concurrent Mode Failure問題而導致觸發(fā)一次Full GC。所謂的浮動垃圾,是由于CMS收集器的并發(fā)清理階段,清理線程是和用戶線程一起運行,如果在清理過程中,用戶線程產生了垃圾對象,由于過了標記階段,所以這些垃圾對象就成為了浮動垃圾,CMS無法在當前垃圾收集過程中集中處理這些垃圾對象。由于這個原因,CMS收集器不能像其他收集器那樣等到完全填滿了老年代以后才進行垃圾收集,需要預留一部分空間來保證當出現浮動垃圾的時候可以有空間存放這些垃圾對象。在JDK 1.5中,默認當老年代使用了68%的時候會激活垃圾收集,這是一個保守的設置,如果在應用中老年代增長不是很快,可以通過參數"-XX:CMSInitiatingOccupancyFraction"控制觸發(fā)的百分比,以便降低內存回收次數來提供性能。在JDK 1.6中,CMS收集器的激活閥值變成了92%。如果在CMS運行期間沒有足夠的內存來存放浮動垃圾,那么就會導致"Concurrent Mode Failure"失敗,這個時候,虛擬機將啟動后備預案,臨時啟動Serial Old收集器來對老年代重新進行垃圾收集,這樣會導致垃圾收集的時間邊長,特別是當老年代內存很大的時候。所以對參數"-XX:CMSInitiatingOccupancyFraction"的設置,過高,會導致發(fā)生Concurrent Mode Failure,過低,則浪費內存空間。

CMS的最后一個問題,就是它在進行垃圾收集時使用的"標記-清除"算法。我們講到"標記-清除"算法,在進行垃圾清理以后,會出現很多內存碎片,過多的內存碎片會影響大對象的分配,會導致即使老年代內存還有很多空閑,但是由于過多的內存碎片,不得不提前觸發(fā)垃圾回收。為了解決這個問題,CMS收集器提供了一個"-XX:+UseCMSCompactAtFullCollection"參數,用于CMS收集器在必要的時候對內存碎片進行壓縮整理。由于內存碎片整理過程不是并發(fā)的,所以會導致停頓時間變長。"-XX:+UseCMSCompactAtFullCollection"參數默認是開啟的。虛擬機還提供了一個"-XX:CMSFullGCsBeforeCompaction"參數,來控制進行過多少次不壓縮的Full GC以后,進行一次帶壓縮的Full GC,默認值是0,表示每次在進行Full GC前都進行碎片整理。

  雖然CMS收集器存在上面提到的這些問題,但是毫無疑問,CMS當前仍然是非常優(yōu)秀的垃圾收集器。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • Java 虛擬機有自己完善的硬件架構, 如處理器、堆棧、寄存器等,還具有相應的指令系統。JVM 屏蔽了與具體操作系...
    尹小凱閱讀 1,749評論 0 10
  • 垃圾收集基礎 Java 語言的一大特點就是可以進行自動垃圾回收處理,而無需開發(fā)人員過于關注系統資源,例如內存資源的...
    Austin_Brant閱讀 820評論 0 2
  • JVM架構 當一個程序啟動之前,它的class會被類裝載器裝入方法區(qū)(Permanent區(qū)),執(zhí)行引擎讀取方法區(qū)的...
    cocohaifang閱讀 1,836評論 0 7
  • Java和C++之間有一堵由內存動態(tài)分配和垃圾收集技術所圍成的“高墻”,墻外面的人想進來,墻里面的人想出來。 對象...
    胡二囧閱讀 1,329評論 0 4
  • 椿去湫來,海棠花開。守護了那么多年,卻抵不過一記回眸。一個追求幸福、快樂,一個承受著絕對的痛苦。也許,這就注定了無...
    心源寶貝閱讀 233評論 0 0

友情鏈接更多精彩內容