1. 程式人生 > >JVM學習和分析(二):GC

JVM學習和分析(二):GC

一、關於GC

  GC是JAVA語言最重要的特性之一,GC為廣大JAVA程式設計師解決了記憶體管理的諸多問題,但GC是一把雙刃劍,在替程式設計師解決了記憶體管理的同時,也隱藏了很多細節,使JAVA程式設計師並不能像C程式設計師那樣對記憶體做到控制。因此,很多時候JAVA程式設計師忽略了對記憶體的管理,認為JAVA可以替程式設計師管理好記憶體,正是因為這樣,JAVA程式會出現很多莫名其妙的問題。

  個人認為,JAVA程式設計師其實在寫程式的時候,本質上就是在和GC做鬥爭,儘可能的去減少GC,但又要保證GC及時有效。

二、常用的基礎GC演算法

1、標記-清除(mark-sweep)演算法

    最基礎的方法,分為標記和清除兩個階段工作

    缺點:效率低下,容易產生大量記憶體碎片,過多記憶體碎片會導致無法分配大物件,從而導致第二次GC

  2、複製(copying)演算法

    將堆記憶體分為相等的兩塊,每次將還存活的物件複製到另外一半記憶體中,將當前記憶體部分的所有物件清除

    優點:解決了標記-清除演算法的效率問題

    缺點:浪費了一半的記憶體空間,且在物件存活率較高時,需要較多的複製操作

  3、標記-整理(mark-compact)演算法

    工作方式和標記-清除演算法類似,首先標記待回收的物件,標記完後,將存活的物件都向記憶體一端移動,然後清除掉邊界外的物件

    優點:避免出現過多的記憶體碎片

    缺點:效率不高

  4、分代收集演算法(目前使用最多的演算法)

    將堆記憶體分為新生代(Eden,S0,S1)和老年代(old),然後根據不同的年代採用不同的垃圾收集演算法

三、GC收集器

GC收集器是真正負責物件回收的部分,不同的GC收集器採用了不同的演算法(組合)

  1、Serial收集器

   最基礎,最古老的收集器,是一個完全的單執行緒收集器。Serial收集器在工作的時候,需要阻塞所有的JAVA執行緒,直到GC工作完成

   新生代:複製演算法

   老年代:標記-整理演算法

   優點:簡單高效,避免執行緒互動的開銷

   缺點:阻塞所有程序

工作場景:client模式下的JVM垃圾收集器,一般情況下,client模式的新生代記憶體在百兆左右,Serial收集時間大約幾十毫秒

  2、ParNew收集器

   多執行緒版本的Serial收集器 

   新生代:複製演算法

   老年代:標記-整理演算法

   優點:簡單高效,避免執行緒互動的開銷

   缺點:阻塞所有程序

   工作場景:server模式下的首先新生代垃圾收集器,配合CMS(作為老年代收集器)組成完成的GC收集器

  3、Parallel Scavenge收集器

   為達到可控制的吞吐量而設計的新生代收集器

   吞吐量=執行使用者程式碼時間/(執行使用者程式碼時間+垃圾收集時間)

   +XX:MaxGCPauseMillis控制最大垃圾收集停頓時間,單位毫秒

   +XX:GCTimeRatio設定吞吐量大小,大於0小於100的整數

   優點:比較準確的控制吞吐量

   缺點:設定不合理的時候,會導致更多的GC

   新生代:複製演算法

   工作場景:需要比較準確的吞吐量的場景

  4、Parallel Old收集器

   Parallel Scavenge的老年代版本

   工作場景:在注重吞吐量及CPU資源敏感的場合,可以考慮使用Parallel Scavenge和Parallel Scavenge組合

  5、CMS收集器

   一個非常優秀的GC收集器,全稱Concurrent Mark Sweep,是基於標記-清除演算法,以獲取最短回收停頓時間為目標的GC收集器,收集過程包括四個階段:

    • 初始標記:標記GC Roots能直接關聯的物件
    • 併發標記:進行GC Roots Tracing
    • 重新標記:去掉併發標記期間,因為使用者繼續使用而產生變化的物件
    • 併發清除

   其中,初始標記和重新標記會阻塞所有執行緒,但由於時間非常短,所以一般不會被使用者察覺

   優點:快速,高效

   缺點:對CPU資源敏感,無法處理浮動垃圾,會產生大量碎片

   工作場景:需要高響應速度的B/S系統

  6、G1收集器

   目前最先進的垃圾收集器,全稱Garbage First,基於標記-整理演算法,G1收集器將堆記憶體分為若干個大小固定的區域,內部維護一個優先列表,在GC時間允許的範圍內,對優先列表中垃圾最多的區域進行回收,從而使GC收集器獲得最高的收集效率。

   正式版本發行在JDK1.7

   優點:收集效率高,長期執行穩定

   缺點:目前沒發現

   工作場景:需要高響應速度的B/S系統

四、個人經驗

CMS和G1都是非常適合應用在高訪問量的系統中,但兩者在實際使用中還是有不少差別,以下對比均為本人在同一套應用上得到

  1、記憶體使用方面

    使用CMS:通常穩定用到一部分堆記憶體,往往在使用一段時間後,會出現記憶體使用量跳變(到現在我還是沒想明白為什麼會出現跳變),跳變幅度一般為1倍,如果原來記憶體使用已經超過了一半,跳變後馬上會出現一次FULL GC,更嚴重的是出現OOM

    使用G1:應用啟動後,瞬間會用滿所有堆記憶體,然後一直會佔用所有堆記憶體,但極少會有FULL GC出現,使用G1後,大約半年才出現了一次FULL GC

  2、GC時間

    使用CMS:YGC大約1-2秒一次,每次大約100ms左右

    使用G1:YGC大約1秒3-4次,每次大約10ms左右

  從記憶體使用和GC時間上來看,G1在高吞吐,高併發的環境下,表現還是非常不錯的