詳見:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt378java
這裏介紹4個垃圾收集器,若是進行了錯誤的選擇將會大大的影響程序的性能。web
時至今日,仍然有兩個事情困擾着開發人員:垃圾收集(GC)和了解異性(程序猿的悲鳴),後者我確實不太瞭解,由於我被前者搞的無暇顧及怎麼了解異性,特別是當知道在JAVA8中對這一區域有了很大的改進和提高還有移除了PermGen和以一些新的使人興奮的調優。算法
當咱們談到垃圾回收時,咱們絕大多數都知道利用它的概念在咱們平常的編程中。可是,當問題出現時,會發現不少是咱們不知道的。JVM 最大誤區之一就是它只有一個垃圾回收器,其實是它提供了四個不一樣的收集器,每一個都有其自身獨特的優點和劣勢。垃圾收集器不是自動選擇的,這取決我的以及吞吐量和應用程序的差別。編程
這些垃圾收集的廣泛存在的共同點是他們都把堆分隔成不一樣的片斷來管理,好比在age-old區中的大多數對象應該被快速的回收。這些都是老生常談的事,咱們直接進入主題來看一下各個收集器的不一樣以及他們的優缺點。數組
Serial收集器是JAVA虛擬機中最基本、歷史最悠久的收集器,在JDK 1.3.1以前是JAVA虛擬機新生代收集的惟一選擇。Serial收集器是一個單線程的收集器,但它的「單線程」的意義並不只僅是說明它只會使用一個CPU或一條收集線程去完成垃圾收集工做,更重要的是在它進行垃圾收集時,必須暫停其餘全部的工做線程,直到它收集結束。服務器
Serial收集器到JDK1.7爲止,它依然是JAVA虛擬機運行在Client模式下的默認新生代收集器。它也有着優於其餘收集器的地方:簡單而高效(與其餘收集器的單線程比),對於限定單個CPU的環境來講,Serial收集器因爲沒有線程交互的開銷,專心作垃圾收集天然能夠得到最高的單線程收集效率。在用戶的桌面應用場景中,分配給虛擬機管理的內存通常來講不會很大,收集幾十兆甚至一兩百兆的新生代(僅僅是新生代使用的內存,桌面應用基本上不會再大了),停頓時間徹底能夠控制在幾十毫秒最多一百多毫秒之內,只要不是頻繁發生,這點停頓是能夠接受的。因此,Serial收集器對於運行在Client模式下的虛擬機來講是一個很好的選擇。多線程
PS:開啓Serial收集器的方式 -XX:+UseSerialGC併發
如:Xms30m -Xmx30m -Xmn10m -XX:+UseSerialGC -XX:+PrintGCDetails性能
-XX:+UseSerialGC的是Serial收集器,Xms30m -Xmx30m 指定了JAVA虛擬機的固定大小爲30M,-Xmn10m 指JAVA新生代的空間爲10M。優化
這是 JVM 的缺省收集器。就像它的名字,其最大的優勢是使用多個線程來經過掃描並壓縮堆。串行收集器在GC時會中止其餘全部工做線程(stop-the-world),CPU利用率是最高的,因此適用於要求高吞吐量(throughput)的應用,但停頓時間(pause time)會比較長,因此對web應用來講就不適合,由於這意味着用戶等待時間會加長。而並行收集器能夠理解是多線程串行收集,在串行收集基礎上採用多線程方式進行GC,很好的彌補了串行收集的不足,能夠大幅縮短停頓時間(以下圖表示的停頓時長高度,併發比並行要短),所以對於空間不大的區域(如young generation),採用並行收集器停頓時間很短,回收效率高,適合高頻率執行。
圖1.Serial收集器與Parallel/ Throughput(並行)收集器的比較
CMS(Concurrent Mark Sweep)收集器是基於「標記-清除」算法實現的,它使用多線程的算法去掃描堆(標記)並對發現的未使用的對象進行回收(清除)。整個過程分爲6個步驟,包括:
初始標記(CMS initial mark)
併發標記(CMS concurrent mark)
併發預清理(CMS-concurrent-preclean)
從新標記(CMS remark)
併發清除(CMS concurrent sweep)
併發重置(CMS-concurrent-reset)
其中初始標記、從新標記這兩個步驟仍然須要「Stop The World」。初始標記僅僅只是標記一下GC Roots能直接關聯到的對象,速度很快,併發標記階段就是進行GC Roots Tracing的過程,而從新標記階段則是爲了修正併發標記期間,因用戶程序繼續運做而致使標記產生變更的那一部分對象的標記記錄,這個階段的停頓時間通常會比初始標記階段稍長一些,但遠比並發標記的時間短。其餘動做都是併發的。
須要注意的是,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後,跟着來一次帶壓縮的。
該算法與並行收集器的另外一個缺點是吞吐量的它使用更多的 CPU,爲了使應用程序提供更好的體驗,經過使用多個線程來執行掃描和收集。這種狀況長時間的運行會使應用程序停頓下來,可使用提升空間來換取高效的運行。可是,這種算法的使用不是默認的。您必須指定 XX: + USeParNewGC來使用它。若是你能夠提供更多的CPU資源的話以免應用程序暫停,那麼你可使用CMS收集器。假設你的堆的大小小於 4 Gb你必須分配大於 4 GB的資源。
G1垃圾收集器在JDK7 update 4以後對大於4G的堆有了更好的支持,G1是一個針對多處理器大容量內存的服務器端的垃圾收集器,其目標是在實現高吞吐量的同時,儘量的知足垃圾收集暫停時間的要求。G1在執行一些Java堆空間中的全區域操做(如:全局標記)時是和應用程序線程併發進行的,所以減小了Java堆空間的中斷比例。(譯者注:可簡單理解爲減小了Stop-the-World的時間比例)。
它與前面的CMS收集器相比有兩個顯著的改進:一是G1收集器是基於「標記-整理」算法實現的收集器,也就是說它不會產生空間碎片,這對於長時間運行的應用系統來講很是重要。二是它能夠很是精確地控制停頓,既能讓使用者明確指定在一個長度爲M毫秒的時間片斷內,消耗在垃圾收集上的時間不得超過N毫秒,具有了一些實時Java(RTSJ)的垃圾收集器的特徵。
首先將Java堆空間劃分爲一些大小相等的區域(region),每一個區域都是虛擬機中的一段連續內存空間。G1經過執行併發的全局標記來肯定整個Java堆空間中存活的對象。標記階段完成後,G1就知道哪些區域基本上是空閒的。在回收內存時優先回收這些區域,這樣一般都會回收至關數量的內存。這就是爲何它叫作Garbage-First的緣由。顧名思義G1關注某些區域的回收和整理,這些區域中的對象頗有可能被徹底回收。並且G1使用了一個暫停時間預測模型使得暫停時間控制在用戶指定的暫停時間內,並根據用戶指定的暫停時間來選擇合適的區域回收內存。
G1肯定了可回收的區域後就是篩選回收(evacuation)階段了。在此階段將對象從一個或多個區域複製到單一區域,同時整理和釋放內存。該階段是在多個處理器上多個線程並行進行的,所以減小了暫停時間並提升了吞吐量。G1在每一次的垃圾收集過程當中都不斷地減小碎片,並可以將暫停時間控制在必定範圍內。這些已是之前的垃圾收集器沒法完成的了。好比:CMS收集器並不作內存整理。ParallelOld收集器只是對整個Java堆空間作整理,這樣致使至關長的暫停時間。
在java8 udpate 20中對G1收集器採用了字符串重複消除技術(String deduplication),以前字符串以及內部的char[]數組大量消耗了內存空間,在新的G1垃圾收集器中,將會對內存中重複的字符串進行優化,使他們指向同一個字符數組,以免相同的字符串出現而使堆處理效率低下,你可使用 -XX:+UseStringDeduplicationJVM參數來開啓。
在 Java 8 最大的變化之一刪除了在堆中爲類的元數據、內部字符串和靜態變量分配 permgen空間的部分。過去若是加載大量的類到內存中常常會出現內存溢出異常,而且開發人員須要在這個方面作大量的工做,因此若是這段經過JVM來管理了將是一個不錯誤的優化。
每一個垃圾收集器都有不一樣的配置參數,能夠經過不一樣的參數來提高性能和下降吞吐量。這些都取決於你的應用需求,不一樣的對收集方式、可忍受的停頓時間、內存的大小都不同,因此要根據自身的需求來定製不一樣的配置參數。