JDK1~JDK13十種垃圾收集器的吐血總結

HotSpot虛擬機提供了多種垃圾收集器,每種收集器都有各自的特點,沒有最好的垃圾收集器,只有最適合的垃圾收集器.我們可以根據自己實際的應用需求選擇最適合的垃圾收集器.

根據新生代和老年代各自的特點,我們應該分別爲它們選擇不同的收集器,以提升垃圾回收效率.

1 Serial垃圾收集器

單線程

只會使用一個CPU或一條GC線程進行垃圾回收,並且在垃圾回收過程中暫停其他所有的工作線程,從而用戶的請求或圖形化界面會出現卡頓.

適合Client模式

一般客戶端應用所需內存較小,不會創建太多的對象,而且堆內存不大,因此垃圾回收時間比較短,即使在這段時間停止一切用戶線程,也不會感到明顯停頓.

簡單高效

由於Serial收集器只有一條GC線程,避免了線程切換的開銷.

採用"複製"算法

2 ParNew垃圾收集器

ParNew是Serial的多線程版本.

2.1 多線程並行執行

ParNew由多條GC線程並行地進行垃圾清理.但清理過程仍然需要暫停一切其他用戶線程.但由於有多條GC線程同時清理,清理速度比Serial有一定的提升.

2.2 適合多CPU的服務器環境

由於使用了多線程,因此適合CPU較多的服務器環境.

  • 與Serial性能對比
    ParNew和Serial唯一區別就是使用了多線程進行垃圾回收,在多CPU的環境下性能比Serial會有一定程度的提升;但線程切換需要額外的開銷,因此在單CPU環境中表現不如Serial,雙CPU環境也不一定就比Serial高效.默認開啓的收集線程數與CPU數量相同.

2.3 採用"複製"算法

2.4 追求"降低停頓時間"

和Serial相比,ParNew使用多線程的目的就是縮短垃圾收集時間,從而減少用戶線程被停頓的時間.

3 Parallel Scavenge垃圾收集器

Parallel Scavenge和ParNew一樣都是並行的多線程、新生代收集器,都使用"複製"算法進行垃圾回收.但它們有個巨大不同點:

  • ParNew收集器追求降低GC時用戶線程的停頓時間,適合交互式應用,良好的反應速度提升用戶體驗.
  • Parallel Scavenge追求CPU吞吐量,能夠在較短的時間內完成指定任務,因此適合不需要太多交互的後臺運算.

吞吐量是指用戶線程運行時間佔CPU總時間的比例.
CPU總時間包括 : 用戶線程運行時間 和 GC線程運行的時間.
因此,吞吐量越高表示用戶線程運行時間越長,從而用戶線程能夠被快速處理完.

  • 降低停頓時間的兩種方式
    1.在多CPU環境中使用多條GC線程,從而垃圾回收的時間減少,從而用戶線程停頓的時間也減少;
    2.實現GC線程與用戶線程併發執行。所謂併發,就是用戶線程與GC線程交替執行,從而每次停頓的時間會減少,用戶感受到的停頓感降低,但線程之間不斷切換意味着需要額外的開銷,從而垃圾回收和用戶線程的總時間將會延長。

  • Parallel Scavenge提供的參數

  • -XX:GCTimeRadio
    直接設置吞吐量大小,GC時間佔總時間比率.相當於是吞吐量的倒數.

  • -XX:MaxGCPauseMillis
    設置最大GC停頓時間.
    Parallel Scavenge會根據這個值的大小確定新生代的大小.如果這個值越小,新生代就會越小,從而收集器就能以較短的時間進行一次回收;但新生代變小後,回收的頻率就會提高,吞吐量也降下來了,因此要合理控制這個值.

  • -XX:+UseAdaptiveSizePolicy
    通過命令就能開啓GC 自適應的調節策略(區別於ParNew).我們只要設置最大堆(-Xmx)和MaxGCPauseMillis或GCTimeRadio,收集器會自動調整新生代的大小、Eden和Survior的比例、對象進入老年代的年齡,以最大程度上接近我們設置的MaxGCPauseMillis或GCTimeRadio.

老年代垃圾收集器

1 Serial Old垃圾收集器

Serial Old收集器是Serial的老年代版本,它們都是單線程收集器,也就是垃圾收集時只啓動一條GC線程,因此都適合客戶端應用.

它們唯一的區別就是Serial Old工作在老年代,使用"標記-整理"算法;而Serial工作在新生代,使用"複製"算法.

2 Parallel Old垃圾收集器

Parallel Old收集器是Parallel Scavenge的老年代版本,一般它們搭配使用,追求CPU吞吐量.
它們在垃圾收集時都是由多條GC線程並行執行,並暫停一切用戶線程,使用"標記-整理"算法.因此,由於在GC過程中沒有使垃圾收集和用戶線程並行執行,因此它們是追求吞吐量的垃圾收集器.

3 CMS垃圾收集器(Concurrent Mark Sweep)

一種追求最短停頓時間的收集器,它在垃圾收集時使得用戶線程和GC線程併發執行,因此在GC過程中用戶也不會感受到明顯卡頓.但用戶線程和GC線程之間不停地切換會有額外的開銷,因此垃圾回收總時間就會被延長.

垃圾回收過程
前兩步需要"Stop The World"

  • 初始標記
    停止一切用戶線程,僅使用一條初始標記線程對所有與GC Roots直接相關聯的對象進行標記,速度很快,因爲沒啥根對象.

  • 併發標記
    使用多條併發標記線程並行執行,並與用戶線程併發執行.此過程進行可達性分析,標記出所有廢棄的對象,速度很慢. 就像你麻麻在你屋子裏收拾垃圾,並不影響你在屋裏繼續浪.這裏也是新一代的收集器努力優化的地方

  • 重新標記
    顯然,你麻麻再怎麼努力收垃圾,你的屋子可能還是一堆被你新生的垃圾!漏標了很多垃圾!所以此時必須 STW,停止一切用戶線程!
    使用多條重新標記線程並行執行,將剛纔併發標記過程中新出現的廢棄對象標記出來.這個過程的運行時間介於初始標記和併發標記之間.

  • 併發清除
    只使用一條併發清除線程,和用戶線程們併發執行,清除剛纔標記的對象.這個過程非常耗時.

  • 線程角度

CMS的缺點

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

  • 無法處理浮動垃圾,導致頻繁Full GC
    由於垃圾清除過程中,用戶線程和GC線程併發執行,也就是用戶線程仍在執行,那麼在執行過程中會產生垃圾,這些垃圾稱爲"浮動垃圾".
    如果CMS在垃圾清理過程中,用戶線程需要在老年代中分配內存時發現空間不足,就需再次發起Full GC,而此時CMS正在進行清除工作,因此此時只能由Serial Old臨時對老年代進行一次Full GC.

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

  • 開啓-XX:+UseCMSCompactAtFullCollection
    開啓該參數後,每次FullGC完成後都會進行一次內存壓縮整理,將零散在各處的對象整理到一塊兒.但每次都整理效率不高,因此提供了以下參數.

  • 設置參數-XX:CMSFullGCsBeforeCompaction
    本參數告訴CMS,經過了N次Full GC過後再進行一次內存整理.

Java9 開始全面廢除 CMS

三色標記算法 - 漏標問題引入

沒有遍歷到的 - 白色
自己標了,孩子也標了 - 黑色
自己標了,孩子還沒標 - 灰色

  • 第一種情況 ,已經標好了 ab,還沒 d,如下,此時B=>D 消失,突然A=D了,因爲 A已黑了,不會再 看他的孩子,於是 D 被漏標了!

漏標的解決方案

把 A 再標成灰色,看起來解決了?其實依然漏標!

CMS方案: Incremental Update的非常隱蔽的問題:
併發標記,依舊產生漏標!


於是產生了 G1!

G1收集器(Garbage-First)

當今最前沿的垃圾收集器成果之一.

G1的特點

  • 追求停頓時間
  • 多線程GC
  • 面向服務端應用
  • 整體來看基於標記-整理和局部來看基於複製算法合併
    不會產生內存空間碎片.
  • 可對整個堆進行垃圾回收
  • 可預測的停頓時間

G1的內存模型

沒有分代概念,而是將Java堆劃分爲一塊塊獨立的大小相等的Region.當要進行垃圾收集時,首先估計每個Region中的垃圾數量,每次都從垃圾回收價值最大的Region開始回收,因此可以獲得最大的回收效率.

Remembered Set

一個對象和它內部所引用的對象可能不在同一個Region中,那麼當垃圾回收時,是否需要掃描整個堆內存才能完整地進行一次可達性分析?
當然不是,每個Region都有一個Remembered Set,用於記錄本區域中所有對象引用的對象所在的區域,從而在進行可達性分析時,只要在GC Roots中再加上Remembered Set即可防止對所有堆內存的遍歷.

G1垃圾收集過程

  • 初始標記
    標記與GC Roots直接關聯的對象,停止所有用戶線程,只啓動一條初始標記線程,這個過程很快.
  • 併發標記
    進行全面的可達性分析,開啓一條併發標記線程與用戶線程並行執行.這個過程比較長.
  • 最終標記
    標記出併發標記過程中用戶線程新產生的垃圾.停止所有用戶線程,並使用多條最終標記線程並行執行.
  • 篩選回收
    回收廢棄的對象.此時也需要停止一切用戶線程,並使用多條篩選回收線程並行執行.

回收算法

依舊前面例子:


因此,還是能追蹤到 D,如果不維護 rset,需要掃描其他所有對象!因此只需要掃描該 region 即可~

針對新生代的垃圾回收器共有三個:Serial,Parallel Scavenge和Parallel New。這三個採用的都是標記-複製算法。其中,Serial是一個單線程的,Parallel New可以看成Serial的多線程版本。Parallel Scavenge和Parallel New類似,但更加註重吞吐率。此外,Parallel Scavenge不能與CMS一起使用。

針對老年代的垃圾回收器也有三個:剛剛提到的Serial Old和Parallel Old,以及CMS。Serial Old和Parallel Old都是標記-壓縮算法。同樣,前者是單線程的,而後者可以看成前者的多線程版本。

CMS採用的是標記-清除算法,並且是併發的。除了少數幾個操作需要Stop-the-world之外,它可以在應用程序運行過程中進行垃圾回收。在併發收集失敗的情況下,Java虛擬機會使用其他兩個壓縮型垃圾回收器進行一次垃圾回收。由於G1的出現,CMS在Java 9中已被廢棄[3]。

G1(Garbage First)是一個橫跨新生代和老年代的垃圾回收器。實際上,它已經打亂了前面所說的堆結構,直接將堆分成極其多個區域。每個區域都可以充當Eden區、Survivor區或者老年代中的一個。它採用的是標記-壓縮算法,而且和CMS一樣都能夠在應用程序運行過程中併發地進行垃圾回收。

G1能夠針對每個細分的區域來進行垃圾回收。在選擇進行垃圾回收的區域時,它會優先回收死亡對象較多的區域。這也是G1名字的由來。

100g內存時,到頭性能.
且G1 浪費空間,fullgc 特別慢!很多階段都是 STW 的,所以有了 ZGC!

ZGC

聽說你是 zerpo paused GC?
Java 11引入了ZGC,宣稱暫停時間不超過10ms,支持 4TB,JDK13 到了 16TB!

和內存無關,TB 級也只停頓 1-10ms

  • UMA

  • NUMA
    知道NUMA存在並且能利用,哪個CPU要分配對象,優先分配離得近的內存

  • 目前不分代(將來可能分冷熱對象)
    ZGC 學習 Asul 的商用C4收集器

顏色指針


原來的GC信息記錄在哪裏呢?對象頭部
ZGC記錄在指針,跟對象無關,因此可以immediate memory reuse
低42位指向對象,2^42=4T JDK13 2^44=16T, 目前最大就 16T,還能再大嗎???
後面四位伐表對象不同狀態m0 m1 remapped finalizable
18爲unused

靈魂問題

內存中有個地址
地址中裝了01001000 , mov 72,到底是一個立即數,還是一條指令?
CPU->內存,通過總線連接,-> 數據總線地址總線控制總線,所以看是從啥總線來的即可
主板地址總線最寬 48bit 48-4 顏色位,就只剩 44 位了,所以最大 16T.

ZGC 階段

1.pause mark start
2.concurrent mark
3.relocate

4.remap

對象的位置改變了,將其引用也改變過去 - 寫屏障(與 JMM 的屏障不同,勿等同!)

而 ZGC 使用的讀屏障!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章