1. 程式人生 > >Java垃圾回收精粹——序列收集器、並行收集器以及併發標記清理收集器(CMS)

Java垃圾回收精粹——序列收集器、並行收集器以及併發標記清理收集器(CMS)

序列收集器(Serial Collector)

序列收集器是最簡單的收集器,對於單處理器系統真是絕佳上選。當然,它也是所有收集器裡面最不常用的。序列收集器使用一個單獨的執行緒進行收集,不管是次要收集還是主要收集。在年老區中分配的物件使用一個簡單的凹凸指標演算法(bump-the-pointer algorithm)即可。當tenured space填滿後會觸發主要回收。

譯註:按照這種技術,JVM內部維護一個allocatedTail指標,始終指向先前已分配物件的尾部。當新的物件分配請求到來時,只需檢查代中剩餘空間,即從allocatedTail到代尾geneTail是否足以容納該物件,並在“滿足”的情況下更新allocatedTail指標和初始化物件。

並行收集器(Parallel Collector)

並行收集器有兩種形式:一種並行收集器(-XX:+ UseParallelGC)在次要回收中使用多執行緒來執行,在主要回收中使用單執行緒執行;另一種是從Java 7u4開始預設使用的並行舊生代收集器(Parallel Old collector )(XX:+UseParallelOldGC),在次要回收和主要回收均使用多執行緒。在tenured space分配的物件使用簡單的凹凸指標(bump-the-pointer)演算法即可。當年老區填滿後會觸發主要回收。

在多處理器系統上,並行舊生代收集器是吞吐量最大的收集器,只有收集開始時才會影響到正在執行的程式。然後使用最高效演算法、多個並行執行緒進行收集。這使得並行舊生代收集器非常適合批處理應用。

回收年老代的成本受存留物件數量影響較大,受堆大小影響較小。要提高並行舊生代收集器的蒐集效率、提供更大的吞吐量,需要更大的記憶體、更長的回收時間、更少的收集時暫停。

這被期望成為最快的次要回收。因為在這個收集器裡,到年老區的晉升是一個簡單的凹凸指標(bump-the-pointer)和複製操作。

對於伺服器應用程式來說,並行舊生代收集器必須是垃圾收集的第一站。如果主要回收的暫停超過了應用程式的容忍下限,需要考慮使用與應用程式併發執行的收集器來收集年老物件。

注意:在現在的硬體條件下,對年老代的壓縮每GB的存活物件預計需要暫停一到五秒。

注意:在多插槽的CPU的伺服器應用程式中設定“-XX:+ UseNUMA”,可以通過並行收集器能獲得更好的效能。這是因為是線上程本地的CPU插槽上分配給Eden記憶體。遺憾的是其他收集器不支援這個功能。

CMS(併發標記清理收集器,Concurrent Mark Sweep)

CMS(-XX:+ UseConcMarkSweepGC)收集器在年老代中使用,專門收集那些在主要回收中不可能到達的年老物件。它與應用程式併發執行,在年老代中保持一直有足夠的空間以保證不會發生年輕代晉升失敗。

晉升失敗將會觸發一次FullGC,CMS會按照下面幾個步驟處理:

  1. 初始化標記:尋找GC根。
  2. 併發標記:標記所有從GC根開始可到達的物件。
  3. 併發預清理:檢查被更新過的物件引用和在併發標記階段晉升的物件。
  4. 重標記:捕捉預清潔階段開始更新的物件引用。
  5. 併發清理:通過回收被死物件佔用的記憶體更新可用空間列表。
  6. 併發重置:重置資料結構為下一次執行做準備。

當年老物件變得不可訪問時,佔用空間會被CMS回收並且放入到空閒空間列表中。當晉升發生的時候,會查詢空閒空間列表,為晉升物件找到大小合適的位置。這增加了晉升的成本,因而相比並行收集器也增加了次要收集的成本。

注意:CMS 不像壓縮收集器,隨著時間的推移會在年老代中產生碎片。物件晉升可能失敗,因為一個大物件可能在年老代在找不到一塊足夠容身的可用空間。如果發生了這樣的事,日誌會記錄一條“晉升失敗”的訊息,然後並且觸發一次FullGC來壓縮存活的年老物件。對於這種壓縮驅動的FullGC,由於CMS使用單執行緒壓縮,可以想見會比使用並行舊生代收集器的主要回收使用更長的暫停時間。

CMS儘可能的與應用程式併發執行,這裡面有幾層含義。首先,CPU的時間會被收集器佔用,因此CPU可用於應用程式的時間片減少。CMS消耗的時間量與到tenured space(老年區)的物件晉升量呈線性正相關。其次,對於併發GC週期中的某些階段,所有的應用執行緒必須到達一個安全點,比如標記GC根並執行並行的重標記來檢查更新。

注意:如果一個應用程式年老區的物件發生非常明顯的變化,重新標記階段將非常耗時,在極端情況下,它可能比一個完整的並行舊生代收集器的壓縮時間還要長。

CMS要想降低FullGC的頻率,可以通過降低吞吐量、使用更耗時的次要回收以及佔用更大的空間的方式實現。 根據不同的晉升率,吞吐量會比並行收集少10%-40%。CMS同樣要求多於20%的空間來存放額外的資料結構和“漂浮垃圾(floating garbage)”,漂浮垃圾是在併發標記階段丟掉的,扔給下一個收集週期處理的物件。

高晉升率以及由此產生的碎片,有時候可以通過增加新生代和年老代空間的大小來減少。

注意:當CMS回收的空間不能滿足晉升需求的時候,它可能遇到“併發模式失敗(concurrent mode failures)”,在日誌中可以找到記錄。產生這種情況可能是因為是收集得太遲,這樣可以通過調整策略來解決。也可能是收集的空間空閒率跟不上高的晉升率或則某些應用超高的物件更新率。如果你程式的晉升率和更新率太高,你可能需要改變你的應用程式來減少晉升的壓力。給CMS加大記憶體有時候會使情況更糟,因為這需要掃描更多的記憶體。