各類垃圾收集器的配合使用關係java
1.Serial收集器算法
Serial收集器是一個單線程收集器,收集時必須暫停其它全部的工做線程,知道收集結束。服務器
雖然Serial收集器比較古老,但目前依然是虛擬機運行在Client模式下的默認新生代收集器。優勢:簡單而高效(與其餘收集器的單線程比),對於限定單個CPU的環境來講,Serial收集器因爲沒有線程交互的開銷,轉系作垃圾收集天然能夠得到最高的單線程收集效率。多線程
Serial收集器對於運行在Client模式下的虛擬機來講是一個很好的選擇。併發
2.ParNew收集器佈局
ParNew收集器就是Serial收集器的多線程版本,Serial收集器可用的全部控制參數(例如:-XX:SurvivoRatio、-XX:PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法。Stop The World、對象分配規則、回收策略等都與Serial收集器徹底同樣。性能
除了多線程沒有太多的創新之處,可是倒是許多運行在Server模式下的虛擬機中首選的新生代收集器,其中有一個與性能無關但很重要的緣由是,除了Serial收集器外,目前只有ParNew收集器能與CMS收集器配合工做。優化
ParNew收集器也是使用-XX:UseConcMarkSweepGC選項後的默認新生代收集器,也可使用-XX:+UseParNewGC選項來強制指定他。操作系統
ParNew在單線程環境中絕對不會比Serial有更好的效果,但隨着CPU數量的增長會有更好的表現。它默認開啓的收集線程數與CPU的數量相同,在CPU很是多的環境下,可使用-XX:ParallelGCThreads參數來限制垃圾收集的線程數。線程
3.Parallel Scavenge收集器
Parallel Scavenge收集器是一個新生代收集器,也是使用複製算法的收集器,又是並行的多線程收集器。它的目標是達到一個可控制的吞吐量(Throuthput)。吞吐量就是CPU用於運行用戶代碼的時間與CPU總消耗時間的比值,即吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間)。
Parallen Scavenge收集器提供了兩個參數用於精確控制吞吐量,分別是控制最大垃圾收集停頓時間的-XX:MaxGCPauseMillis參數以及直接設置吞吐量大小的-XX:GCTimeRatio參數。
MaxGCPauseMillis參數容許的值是一個大於0的毫秒數,收集器將盡力保證內存回收花費的時間不超過設定值。不過你們不要異想天開地認爲若是把這個參數的值設置得稍小一點就能使得系統的垃圾收集速度變得更快,GC停頓時間縮短是以犧牲吞吐量和新生代空間來換取的:系統把新生代調小一些,收集300MB新生代確定比收集500MB快吧,這也直接致使垃圾收集發生得更頻繁一些,原來10秒收集一次、每次停頓100毫秒,如今變成5秒收集一次、每次停頓70毫秒。停頓時間的確在降低,但吞吐量也降下來了。
GCTimeRatio參數的值應當是一個大於0小於100的整數,也就是垃圾收集時間佔總時間的比率,至關因而吞吐量的倒數。若是把此參數設置爲19,那容許的最大GC時間就佔總時間的5%(即1 /(1+19)),默認值爲99,就是容許最大1%(即1 /(1+99))的垃圾收集時間。
Parallel Scavenge收集器還有一個參數-XX:+UseAdaptiveSizePolicy值得關注。這是一個開關參數,當這個參數打開以後,就不須要手工指定新生代的大小(-Xmn)、Eden與Survivor區的比例(-XX:SurvivorRatio)、晉升老年代對象年齡(-XX:PretenureSizeThreshold)等細節參數了,虛擬機會根據當前系統的運行狀況收集性能監控信息,動態調整這些參數以提供最合適的停頓時間或最大的吞吐量,這種調節方式稱爲GC自適應的調節策略(GC Ergonomics)。若是讀者對於收集器運做原理不太瞭解,手工優化存在困難的時候,使用Parallel Scavenge收集器配合自適應調節策略,把內存管理的調優任務交給虛擬機去完成將是一個很不錯的選擇。只須要把基本的內存數據設置好(如-Xmx設置最大堆),而後使用MaxGCPauseMillis參數(更關注最大停頓時間)或GCTimeRatio參數(更關注吞吐量)給虛擬機設立一個優化目標,那具體細節參數的調節工做就由虛擬機完成了。自適應調節策略也是Parallel Scavenge收集器與ParNew收集器的一個重要區別。
4.Serial Old收集器
Serial Old收集器是Serial收集器的老年代版本,也是一個單線程收集器,使用「標記-整理」算法。主要意義也是給在Client模式下的虛擬機使用。Server模式下:一種是在JDK1.5以前版本中與Parallel Scavenge收集器搭配使用,另外一種是做爲CMS收集器的後備預案,在併發收集發生Concurrent Mode Failure時使用。
5.Parallel Old收集器
Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多線程和「標記-整理」算法。這個收集器在JDK1.6以後纔開始提供,在此以前若是新生代選擇了Parallel Scavenge收集器,老年代除了Serial Old(PS MarkSweep)收集器外別無選擇。因爲單線程的老年代Serial Old收集器在服務端應用性能上的「拖累」,即使使用了Parallel Scavenge收集器也未必能在總體應用上得到吞吐量最大化的效果,又由於老年代收集中沒法充分利用服務器多CPU的處理能力,在老年代很大並且硬件比較高級的環境中,這種組合的吞吐量甚至還不必定有ParNew加CMS的組合「給力」。
6.CMS收集器
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器。是「標記-清除」算法實現的。
收集過程分爲4個步驟:
①初始標記(CMS initial mark)
②併發標記(CMS concurrent mark)
③從新標記(CMS remark)
④併發清除(CMS concurrent sweep)
初始標記和從新標記須要「Stop The World」。初始標記僅僅標記一下GC Roots能直接關聯到的對象,速度很快,併發標記階段就是進行GC Roots Tracing的過程,從新標記階段是爲了修正併發標記期間因用戶程序繼續運做而致使標記產生變更的那一部分對象的標記記錄,這個階段的停頓時間通常會比初始標記階段稍長一些,但遠比並發標記的時間短。
CMS收集器優勢:
併發收集、低停頓。
缺點:
CMS收集器對CPU資源很是敏感。其實,面向併發設計的程序都對CPU資源比較敏感。在併發階段,它雖然不會致使用戶線程停頓,可是會由於佔用了一部分線程(或者說CPU資源)而致使應用程序變慢,總吞吐量會下降。CMS默認啓動的回收線程數是(CPU數量+3)/ 4,也就是當CPU在4個以上時,併發回收時垃圾收集線程最多佔用不超過25%的CPU資源。可是當CPU不足4個時(譬如2個),那麼CMS對用戶程序的影響就可能變得很大,若是CPU負載原本就比較大的時候,還分出一半的運算能力去執行收集器線程,就可能致使用戶程序的執行速度突然下降了50%,這也很讓人受不了。爲了解決這種狀況,虛擬機提供了一種稱爲「增量式併發收集器」(Incremental Concurrent Mark Sweep / i-CMS)的CMS收集器變種,所作的事情和單CPU年代PC機操做系統使用搶佔式來模擬多任務機制的思想同樣,就是在併發標記和併發清理的時候讓GC線程、用戶線程交替運行,儘可能減小GC線程的獨佔資源的時間,這樣整個垃圾收集的過程會更長,但對用戶程序的影響就會顯得少一些,速度降低也就沒有那麼明顯,可是目前版本中,i-CMS已經被聲明爲「deprecated」,即再也不提倡用戶使用。
CMS收集器沒法處理浮動垃圾(Floating Garbage),可能出現「Concurrent Mode Failure」失敗而致使另外一次Full GC的產生。因爲CMS併發清理階段用戶線程還在運行着,伴隨程序的運行天然還會有新的垃圾不斷產生,這一部分垃圾出如今標記過程以後,CMS沒法在本次收集中處理掉它們,只好留待下一次GC時再將其清理掉。這一部分垃圾就稱爲「浮動垃圾」。也是因爲在垃圾收集階段用戶線程還須要運行,即還須要預留足夠的內存空間給用戶線程使用,所以CMS收集器不能像其餘收集器那樣等到老年代幾乎徹底被填滿了再進行收集,須要預留一部分空間提供併發收集時的程序運做使用。在默認設置下,CMS收集器在老年代使用了68%的空間後就會被激活,這是一個偏保守的設置,若是在應用中老年代增加不是太快,能夠適當調高參數-XX:CMSInitiatingOccupancyFraction的值來提升觸發百分比,以便下降內存回收次數以獲取更好的性能。要是CMS運行期間預留的內存沒法知足程序須要,就會出現一次「Concurrent Mode Failure」失敗,這時候虛擬機將啓動後備預案:臨時啓用Serial Old收集器來從新進行老年代的垃圾收集,這樣停頓時間就很長了。因此說參數-XX:CMSInitiatingOccupancyFraction設置得過高將會很容易致使大量「Concurrent Mode Failure」失敗,性能反而下降。
還有最後一個缺點,在本節在開頭說過,CMS是一款基於「標記-清除」算法實現的收集器,若是讀者對前面這種算法介紹還有印象的話,就可能想到這意味着收集結束時會產生大量空間碎片。空間碎片過多時,將會給大對象分配帶來很大的麻煩,每每會出現老年代還有很大的空間剩餘,可是沒法找到足夠大的連續空間來分配當前對象,不得不提早觸發一次Full GC。爲了解決這個問題,CMS收集器提供了一個-XX:+UseCMSCompactAtFullCollection開關參數,用於在「享受」完Full GC服務以後額外免費附送一個碎片整理過程,內存整理的過程是沒法併發的。空間碎片問題沒有了,但停頓時間不得不變長了。虛擬機設計者們還提供了另一個參數-XX: CMSFullGCsBeforeCompaction,這個參數用於設置在執行多少次不壓縮的Full GC後,跟着來一次帶壓縮的。
7.G1收集器
G1(Garbage-First)是一款面向服務端應用的垃圾收集器。使命是替換掉CMS收集器。
使用G1收集器時,java堆的內存佈局與其餘收集器有很大差異,它將整個java堆分爲多個大小相等的獨立區域(Region),雖然還保留新生代和老年代的概念,但已經不是物理隔離的了,都是一部分Region(不須要連續)的集合。
G1能創建可預測的停頓時間模型。它能夠有計劃的避免在整個java堆中進行全區域的垃圾收集。G1跟蹤各個Region裏面的垃圾堆積的價值大小(回收所得到的空間大小以及回收所須要的時間的經驗值),在後臺維護一個優先列表,每次根據容許的收集時間,優先回收價值最大的Region(這也就是Garbaage-First名稱的由來)。
其中的細節,Region不多是孤立的,一個對象分配在某個Region中,他並不是只能被本Region中的其餘對象引用,而是能夠與整個java堆任意的對象發生引用關係。那豈不是判斷對象存活的時候須要掃描整個java堆。使用Remembered Set來避免全局掃描。G1中每一個Region都有一個與之對應的Remembered Set,虛擬機發現程序在對Reference類型的數據進行寫操做時,會產生一個Write Barrier暫時中斷寫操做,檢查Reference引用的對象是否處於不一樣的Region之中(在分代例子中就是檢查是否老年代中的對象應用了新生代中的對象),若是是,便經過CardTable把相關引用信息記錄到被應用對象所屬的Region的Remembered Set中。當進行內存回收時,在GC根節點的枚舉範圍中加入Remembered Set便可保證不對全堆掃描也不會有遺漏。
不計算維護Remembered Set的操做,G1收集器的運做大體可劃分爲一下幾個步驟:
①初始標記(Initial Marking)
②併發標記(Concurrent Marking)
③最終標記(Final Marking)
④篩選回收(Live Data Counting and Evacuation)
前兩個步驟與CMS收集器操做同樣。
最終標記是爲了修正在併發標記期間因用戶程序繼續運做而致使標記產生變更的那一部分標記記錄,虛擬機將這段時間對象的變化記錄在線程Remembered Set Logs裏面,最終標記階段須要把Remembered Set Logs的數據合併到Remembered Set中,這階段須要停頓線程,可是可並行執行。
最後在篩選回收階段首先對各個Region的回收價值和成本進行排序,根據用戶所指望的GC停頓時間來制定回收計劃,這個階段其實也能夠作到與用戶程序一塊兒併發執行,可是由於只回收一部分Region,時間是用戶可控制的,並且停頓用戶線程將大幅提升收集效率。
做爲CMS和G1的選擇,若是你的應用追求低停頓,那G1如今已經能夠做爲一個能夠嘗試的選擇,若是你的應用追求吞吐量,那G1並無什麼優點。若是你如今採用的收集器沒有出現問題,那就沒有任何理由如今去選擇G1。