1. 程式人生 > >自動記憶體管理機制(3)-HotSpot垃圾收集器

自動記憶體管理機制(3)-HotSpot垃圾收集器

自動記憶體管理機制(3)-HotSpot垃圾收集器

如果說收集演算法是記憶體回收的方法論,那麼垃圾收集器就是記憶體回收的具體實現。

這裡討論的收集器都是JDK1.7(包含JDK1.7)以後的HotSpot虛擬機器:

在這裡插入圖片描述
上半部屬於新生代收集器,下半部屬於老年代收集器。如果兩個收集器之間存在連線,說明他們可以搭配使用。

1. Serial 收集器

  1. 單執行緒

    只開啟一條GC執行緒進行垃圾回收,並且在垃圾回收過程中停止一切使用者執行緒,從而使用者的請求或圖形化頁面會出現卡頓。

  2. 適合客戶端使用

    一般客戶端應用所需記憶體較小,不會建立太多的物件,而且堆記憶體不大,因此垃圾回收時間比較短,即使在這段時間停止一切使用者執行緒,使用者也不會感受到明顯的卡頓,因此本垃圾收集器適合客戶端使用。

  3. 簡單高效

    由於Serial收集器只有一條GC執行緒,因此避免了執行緒切換的開銷,從而簡單高效。

  4. 採用“複製演算法”

2. ParNew 收集器

  1. 多執行緒並行執行

    其實就是Serial 收集器的多執行緒版本。

    ParNew由多條GC執行緒並行的進行垃圾清理。但清理過程仍然需要停止一切使用者執行緒。但由於有多條GC執行緒同時清理,清理速度比Serial有一定的提升。

  2. 適合多CPU的伺服器環境

    由於使用了多執行緒,因此適合CPU較多的伺服器環境

  3. 與Serial效能對比

    ParNew和Serial唯一的區別就是使用了多執行緒進行垃圾回收,在多CPU的環境下效能比Serial會有一定程度的提升;但執行緒切換需要額外的開銷,因此在單CPU環境中表現不如Serial。

  4. 採用“複製演算法”

  5. 追求“降低停頓時間”

    和Serial相比,ParNew使用多執行緒的目的就是縮短垃圾收集時間,從而減少使用者執行緒被停頓的時間。

3. Parallel Scavenge 收集器

Parallel Scavenge和ParNew一樣都是多執行緒、新生代收集器,都使用“複製”演算法進行垃圾回收。ParNew收集器追求降低使用者執行緒的停頓時間,因此適合互動式應用,而Parallel Scavenge追求CPU吞吐量,能夠在較短的時間內完成指定任務,因此適合沒有互動的後臺計算。

  1. 什麼是吞吐量?

    吞吐量是指使用者執行緒執行時間佔CPU總時間的比例

    CPU總時間包括:使用者執行緒執行時間、GC執行緒執行時間。

    因此,吞吐量越高表示使用者執行緒執行時間越長,從而使用者執行緒能夠被快速處理完。

  2. 降低停頓時間的兩種方式

    1. 在多CPU環境中使用多條GC執行緒,從而垃圾回收的時間減少,使用者執行緒停頓時間也減少。
    2. 實現GC執行緒與使用者執行緒併發執行。
  3. Parallel Scavenge提供的引數

    1. 設定“吞吐量”

      通過引數-XX:GCTimeRadio設定垃圾回收時間佔總CPU時間的百分比。

    2. 設定“停頓時間”

      通過引數-XX:MaxGCPauseMillis設定垃圾處理過程最久停頓時間。Parallel Scavenge會根據這個值的大小確定新生代的大小。如果這個值越小,新生代就會越小,從而收集器就能以較短的時間進行一次回收。但新生代變小後,回收的頻率就會提高,因此要合理控制這個值。

    3. 啟用自適應調節策略

      通過命令-XX:+UseAdaptiveSizePolicy就能開啟自適應策略。我們只要設定好堆的大小和MaxGCPauseMillis或GCTimeRadio,收集器會自動調整新生代的大小、Eden和Survior的比例、物件進入老年代的年齡,以最大程度上接近我們設定的MaxGCPauseMillis或GCTimeRadio。

4. Serial Old 收集器

Serial Old是Serial收集器的老年代版本,它同樣是一個單執行緒收集器,使用“標記 - 整理”演算法。主要給Client模式(客戶端)下的虛擬機器使用,如果在Server模式(伺服器)下,有以下兩種用途:

  1. 在JDK1.5以及之前的版本中與Parallel Scavenge收集器搭配使用
  2. 作為CMS收集器的後備預案,在併發收集發生Concurrent Mode Failure時使用

5. Parallel Old 收集器

Parallel Old 是 Parallel Scavenge 收集器的老年代版本,使用多執行緒和“標記 - 整理”演算法,他們兩一般是搭配使用的,追求CPU吞吐量。

6. CMS 收集器

CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器。它是基於“標記 - 清除”演算法實現的,整個運作過程分為四個步驟:

  1. 初始標記(CMS initial mark)

    停止一切使用者執行緒,僅使用一條初始標記執行緒對所有與GC ROOTS直接關聯的物件進行標記。速度很快。

  2. 併發標記(CMS concurrent mark)

    使用多條併發標記執行緒並行執行,並與使用者執行緒併發執行。此過程進行可達性分析,標記出所有廢棄的物件。速度很慢。

  3. 重新標記(CMS remark)

    停止一切使用者執行緒,並使用多條重新標記執行緒並行執行,將剛才併發標記過程中新出現的廢棄物件標記出來。這個過程的執行時間介於初始標記和併發標記之間。

  4. 併發清除(CMS concurrent sweep)

    只使用一條併發清除執行緒,和使用者執行緒們併發執行,清除剛才標記的物件。這個過程非常耗時。

CMS的缺點

  1. 吞吐量低

    由於CMS在垃圾收集過程使用使用者執行緒和GC執行緒並行執行,從而執行緒切換會有額外開銷,因此CPU吞吐量就不如在垃圾收集過程中停止一切使用者執行緒的方式來的高。

  2. 無法處理浮動垃圾,導致頻繁Full GC

    由於垃圾清除過程中,使用者執行緒和GC執行緒併發執行,也就是使用者執行緒仍在執行,那麼在執行過程中會產生垃圾,這些垃圾稱為“浮動垃圾”。

    如果CMS在垃圾清理過程中,使用者執行緒需要在老年代中分配記憶體時發現空間不足時,就需要再次發起Full GC,而此時CMS正在進行清除工作,因此此時只能由Serial Old臨時對老年代進行一次Full GC。

  3. 使用“標記-清除”演算法產生碎片空間

    由於CMS使用了“標記-清除”演算法, 因此清除之後會產生大量的碎片空間,不利於空間利用率。不過CMS提供了應對策略:

    • 開啟-XX:+UseCMSCompactAtFullCollection

      開啟該引數後,每次FullGC完成後都會進行一次記憶體壓縮整理,將零散在各處的物件整理到一塊兒。

    • 設定引數-XX:CMSFullGCsBeforeCompaction

      本引數告訴CMS,經過了N次Full GC過後再進行一次記憶體整理。

7. G1 收集器

7.1. G1的特點

  • 並行與併發:其他收集器需要停頓執行的GC動作,G1仍然可以通過併發的方式讓Java程式繼續執行。
  • 分代收集:雖然G1不需要和其他收集器配合,但它仍保留了分代的概念。
  • 空間整合:G1是一種“標記 - 整理”演算法和“複製”演算法整合的收集器,不會產生記憶體空間碎片。
  • 可預測的停頓

7.2. G1的記憶體模型

使用G1收集器時,不再劃分原有的新生代或者老年代,它將整個Java堆劃分為多個大小相等的獨立區域(Region),雖然還保留新生代和老年代的概念,但兩者之間不再是物理隔離了,它們都是Region的集合。

7.3. G1的執行過程

  1. 初始標記

    標記與GC ROOTS直接關聯的物件,停止所有使用者執行緒,只啟動一條初始標記執行緒,這個過程很快。

  2. 併發標記

    進行全面的可達性分析,開啟一條併發標記執行緒與使用者執行緒並行執行。這個過程比較長。

  3. 最終標記

    標記出併發標記過程中使用者執行緒新產生的垃圾。停止所有使用者執行緒,並使用多條最終標記執行緒並行執行。

  4. 篩選回收

    回收廢棄的物件。此時也需要停止一切使用者執行緒,並使用多條篩選回收執行緒並行執行。