JVM 垃圾回收

    從JVM工做原理中咱們知道,Class文件被加載到JVM內存後會產生各類對象,像類靜態信息、方法等會被存儲在方法區,對象則會被存儲在堆上,程序運行一段時間後,有些對象已經不會再被引用,因此須要被釋放掉,這部分工做正是垃圾回收的功能。開發者須要理解垃圾回收機制,而且進行些優化設置,從而使JVM垃圾回收器更好的工做。

垃圾回收總體架構:


1. 垃圾回收分類:
 
  垃圾回收分類的標準有三個:回收策略,分區方式和系統線程,垃圾回收器通常根據這三個標準組合使用,例如CMS垃圾回收器使用標記清除,分代分區和併發收集來進行垃圾回收。java

  • 根據基本回收策略
    a) 引用計數(Reference counting)。此算法的原理是對象有一個引用,即增長一個計數,刪除一個引用則減小一個計數。垃圾回收時,引用收集計數爲0的對象。此算法最致命的是沒法處理循環引用的問題。
     
    b) 標記清除(Mark-Sweep)。此算法執行分兩階段。第一階段從引用根節點開始標記全部被引用的對象,第二階段遍歷整個堆,把未標記的對象清除。此算法須要暫停整個應用,同時會產生內存碎片。

    c) 複製(Copying)。此算法把內存空間劃爲兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象複製到另一個區域中。次算法每次只處理正在使用中的對象,所以複製成本比較小,同時複製過去之後還能進行相應的內存整理,不會出現「碎片」問題。固然,此算法的缺點也是很明顯的,就是須要兩倍內存空間。

    d) 標記整理(Mark-Compact)。此算法結合了「標記-清除」和「複製」兩個算法的優勢。也是分兩階段,第一階段從根節點開始標記全部被引用對象,第二階段遍歷整個堆,把清除未標記對象而且把存活對象「壓縮」到堆的其中一塊,按順序排放。此算法避免了「標記-清除」的碎片問題,同時也避免了「複製」算法的空間問題。
     
  • 根據分區方式
    a) 增量收集(Incremental Collecting)。實時垃圾回收算法,即在應用進行的同時進行垃圾回收。

    b) 分代收集(Generational Collecting)。基於對對象生命週期分析後得出的垃圾回收算法。把對象分爲年青代、年老代、持久代,對不一樣生命週期的對象使用不一樣的算法進行回收。如今的垃圾回收器(從J2SE1.2開始)都是使用此算法的。
     
  • 根據系統線程
    a) 串行收集(Serial)。串行收集使用單線程處理全部垃圾回收工做,由於無需多線程交互,實現容易,並且效率比較高。可是,其侷限性也比較明顯,即沒法使用多處理器的優點,因此此收集適合單處理器機器。固然,此收集器也能夠用在小數據量(100M左右)狀況下的多處理器機器上。

    b) 並行收集(Parallel)。並行收集使用多線程處理垃圾回收工做,於是速度快,效率高。並且理論上CPU數目越多,越能體現出並行收集器的優點。

    c) 併發收集(Concurrent)。相對於串行收集和並行收集而言,前面兩個在進行垃圾回收工做時,須要暫停整個運行環境,而只有垃圾回收程序在運行,所以,系統在垃圾回收時會有明顯的暫停,並且暫停時間會由於堆越大而越長。


2. 分代方式:
   
目前大部分垃圾回收器都是經過分代方式來實現垃圾回收,回收策略和系統線程能夠進行選擇搭配。JVM內存分代方式如圖所示:

    虛擬機中的共劃分爲三個代:年輕代(Young Generation)、年老點(Old Generation)、持久代(Permanent Generation),其中持久代主要存放的是Java類的類信息,與垃圾收集要收集的Java對象關係不大。年輕代和年老代的劃分是對垃圾收集影響比較大的。linux

  •  年輕代(Young Generation)
    全部新生成的對象首先都是放在年輕代的。年輕代的目標就是儘量快速的收集掉那些生命週期短的對象。年輕代分三個區。一個Eden區,兩個Survivor區from和to。大部分對象在Eden區中生成。當Eden區滿時,還存活的對象將被複制到to,from中也會拷貝到to,若是to滿時,對象將被拷貝到「年老區(Tenured)」,此時,Eden和to的對象就是垃圾,能夠直接清除,to則是存活的對象,下次YGC時to和from角色互調換
     
  • 年老代(Old Generation)
    在年輕代中經歷了N次垃圾回收後仍然存活的對象,就會被放到年老代中。所以,能夠認爲年老代中存放的都是一些生命週期較長的對象。
     
  • 持久代(Permanent Generation)Java8 中已經取消,由MetaSpace代替
    用於存放靜態文件,現在Java類、方法等。持久代對垃圾回收沒有顯著影響,可是有些應用可能動態生成或者調用一些class,例如hibernate等,在這種時候須要設置一個比較大的持久代空間來存放這些運行過程當中新增的類。持久代大小經過-XX:MaxPermSize=<N>進行設置。
     
  • 元空間(MetaSpace)
    用於存放靜態數據,現在Java類、方法,靜態成員等。元空間並不在虛擬機中,而是使用本地內存。所以,默認狀況下,元空間的大小僅受本地內存限制,但能夠經過如下參數來指定元空間的大小。元空間經過-XX:MetaspaceSize, -XX:MaxMetaspaceSize進行配置。


3. 垃圾回收算法

     GC有兩種類型:Young GC和Full GC,Young GC只清理Young Generation,Full GC會清理全部Generation。數據結構

  • Young GC(minor gc)
    通常狀況下,當新對象生成,而且在Eden申請空間失敗時,就會觸發Young GC,對Eden區域進行GC,清除非存活對象,而且把尚且存活的對象移動到Survivor區。而後整理Survivor的兩個區。這種方式的GC是對年輕代的Eden區進行,不會影響到年老代。由於大部分對象都是從Eden區開始的,同時Eden區不會分配的很大,因此Eden區的GC會頻繁進行。於是,通常在這裏須要使用速度快、效率高的算法,使Eden去能儘快空閒出來。
     
  • Full GC (major gc)
    對整個堆進行整理,包括Young、Tenured和Perm。Full GC由於須要對整個對進行回收,因此比Young GC要慢,所以應該儘量減小Full GC的次數。在對JVM調優的過程當中,很大一部分工做就是對於FullGC的調節。
     

4. 垃圾回收器
    垃圾回收器按照垃圾回收類型進行分類:多線程

  • Young GC
    a) 串行GC(Serial Copying)。client模式下的默認GC方式,也可以使用-XX:+UseSerialGC指定。
    b) 並行回收GC(Parallel Scavenge)。server模式下的默認GC方式,也可用-XX:+UseParallelGC強制指定。
    c) 並行GC(ParNew)。CMS GC時默認採用,會配合CMS作些處理,也能夠採用-XX:+UseParNewGC指定。
     
  • Old GC
    a) 串行GC(Serial MSC)。client模式下的默認GC方式,可經過-XX:+UseSerialGC強制指定。每次進行所有回收,進行Compact,很是耗費時間。
    b) 並行GC(Parallel MSC)。server模式下的默認GC方式,也可用-XX:+UseParallelGC=強制指定。能夠在選項後加等號來制定並行的線程數。
    c) 併發GC(CMS)。使用CMS是爲了減小GC執行時的停頓時間,垃圾回收線程和應用線程同時執行,可使用-XX:+UseConcMarkSweepGC=指定使用,後邊接等號指定併發線程數,CMS分6個階段,初始標記和重標記是STW,其餘4個階段則是併發進行。碎片是因爲CMS默認不對內存進行Compact所致,能夠經過-XX:+UseCMSCompactAtFullCollection和-XX:+UseCMSCompactAtFullCollection=0。
    d) G1收集器(Garbage First)。與 CMS 收集器相比,G1 收集器是基於標記-壓縮算法的。所以,它不會產生空間碎片,也沒有必要在收集完成後,進行一次獨佔式的碎片整理工做。G1 收集器還能夠進行很是精確的停頓控制。它可讓開發人員指定當停頓時長爲 M 時,垃圾回收時間不超過 N。使用參數-XX:+UnlockExperimentalVMOptions –XX:+UseG1GC 來啓用 G1 回收器,設置 G1 回收器的目標停頓時間:-XX:MaxGCPauseMills=20,-XX:GCPauseIntervalMills=200。


5. CMS垃圾回收過程架構

  • 初始標記(initial mark) 會STW
    這個階段從垃圾回收的根引用開始,只掃描到可以和根引用直接關聯的對象(包括棧中變量表,方法區靜態屬性引用對象,常量區引用對象,JNI引用對象),並做標記,主要包括棧引用標記和年輕代引用標記,棧引用標記會在ms內完成,棧引用標記取決於Eden區大小,因此能夠經過設置-XX:CMSWaitDuration=等待時間,在初始標記以前進行YGC。這個過程會STW,可是很快就完成了。
     
  • 併發標記(concurrent mark)
    這個階段緊隨初始標記階段,在初始標記的基礎上繼續向下追溯標記。併發標記階段,應用程序的線程和併發標記的線程併發執行,因此用戶不會感覺到停頓。
     
  • 併發預清理(concurrent preclean)
    在這個階段,虛擬機查找在執行併發標記階段新進入老年代的對象(可能會有一些對象重新生代晉升到老年代, 或者有一些對象被分配到老年代,這樣下一階段可能不須要掃描不少對象)。經過從新掃描,減小下一個階段從新標記的工做,由於下一個階段會STW
     
  • 從新標記 (remark)  會STW
    這個階段會STW,收集器線程掃描在CMS堆中剩餘的對象。掃描從根引用開始向下追溯,並處理對象關聯。回收器並不須要遍歷所有的對象圖,只須要遍歷從標記開始到當前發生變化的引用便可,同時,線程棧和年輕代須要從新掃描一遍。一般狀況下,從新標記的大多數時間,都消耗在掃描年輕代,能夠經過設置-XX:CMSScavengeBeforRemark牆紙從新標記前進行YGC。
     
  • 併發清理 (concurrent sweep)
    這個階段收集器線程和應用程序線程併發執行,全部再也不被引用的對象將從堆裏清除掉。
     
  • 併發重置(concurrent reset)
    這個階段,重置CMS收集器的數據結構,等待下一次垃圾回收。


6. GC 相關參數併發

  • 與串行回收器相關的參數
    -XX:+UseSerialGC:在新生代和老年代使用串行回收器。
    -XX:+SuivivorRatio:設置 eden 區大小和 survivor 區大小的比例。
    -XX:+PretenureSizeThreshold:設置大對象直接進入老年代的閾值。當對象的大小超過這個值時,將直接在老年代分配。
    -XX:MaxTenuringThreshold:設置對象進入老年代的年齡的最大值。每一次 Minor GC 後,對象年齡就加 1。任何大於這個年齡的對象,必定會進入老年代。
     
  • 與並行 GC 相關的參數
    -XX:+UseParNewGC: 在新生代使用並行收集器。
    -XX:+UseParallelOldGC: 老年代使用並行回收收集器。
    -XX:ParallelGCThreads:設置用於垃圾回收的線程數。一般狀況下能夠和 CPU 數量相等。但在 CPU 數量比較多的狀況下,設置相對較小的數值也是合理的。
    -XX:MaxGCPauseMills:設置最大垃圾收集停頓時間。它的值是一個大於 0 的整數。收集器在工做時,會調整 Java 堆大小或者其餘一些參數,儘量地把停頓時間控制在 MaxGCPauseMills 之內。
    -XX:GCTimeRatio:設置吞吐量大小,它的值是一個 0-100 之間的整數。假設 GCTimeRatio 的值爲 n,那麼系統將花費不超過 1/(1+n) 的時間用於垃圾收集。
    -XX:+UseAdaptiveSizePolicy:打開自適應 GC 策略。在這種模式下,新生代的大小,eden 和 survivor 的比例、晉升老年代的對象年齡等參數會被自動調整,以達到在堆大小、吞吐量和停頓時間之間的平衡點。
     
  • 與 CMS 回收器相關的參數
    -XX:+UseConcMarkSweepGC: 新生代使用並行收集器,老年代使用CMS+串行收集器。
    -XX:+ParallelCMSThreads: 設定 CMS 的線程數量。
    -XX:+CMSInitiatingOccupancyFraction:設置 CMS 收集器在老年代空間被使用多少後觸發,默認爲 68%。
    -XX:+UseFullGCsBeforeCompaction:設定進行多少次 CMS 垃圾回收後,進行一次內存壓縮。
    -XX:+CMSClassUnloadingEnabled:容許對類元數據進行回收。
    -XX:+CMSParallelRemarkEndable:啓用並行重標記。
    -XX:CMSInitatingPermOccupancyFraction:當永久區佔用率達到這一百分比後,啓動 CMS 回收 (前提是-XX:+CMSClassUnloadingEnabled 激活了)。
    -XX:UseCMSInitatingOccupancyOnly:表示只在到達閾值的時候,才進行 CMS 回收。
    -XX:CMSWaitDuration:初始標記前進行YGC
    -XX:CMSScavengeBeforRemark:重標記前進行YGC。
    -XX:+CMSIncrementalMode:使用增量模式,比較適合單 CPU。
     
  • 與 G1 回收器相關的參數
    -XX:+UseG1GC:使用 G1 回收器。
    -XX:+UnlockExperimentalVMOptions:容許使用實驗性參數。
    -XX:+MaxGCPauseMills:設置最大垃圾收集停頓時間。
    -XX:+GCPauseIntervalMills:設置停頓間隔時間。
     
  • 其餘參數
    -XX:+DisableExplicitGC: 禁用顯示 GC。
     

7. 通常狀況下設置
    -Xmx(size)  // 官方默認爲內存1/4,linux不能超過3/4
    -Xms(size)  // 官方默認爲內存1/64
    -Xmn(0.25*size ~ 0.5*size)  // 官方推薦爲堆的3/8
    -XX:+UseParNewGC
    -XX:+UseConcMarkSweepGC
    -XX:CMSInitiatingOccupancyFraction=70 // 使用了多少後進行full gc,默認92%
    -XX:CMSFullGCsBeforeCompaction=0    // 多少次full gc後對年老代進行壓縮

    Xmx大小等於Xms大小,避免堆大小調整
    Xmn年代的大小在1/4~1/2堆大小之間,避免由於年輕代過小,數據大部分移到年老代,最後致使頻繁Full GC
    Young GC和Full GC分別採用並行GC和併發GC,充分利用多CPU,新版的JVM中-XX:+UseConcMarkSweepGC默認打開-XX:+UseParNewGC
    Full GC後要進行整理壓縮,減小年老區碎片


    java -Xmx1024m -Xms1024m -Xmn400m -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=0 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:/home/parallels/gc.log -jar server.jar優化

相關文章
相關標籤/搜索