Java虛擬機-4-垃圾回收器

四、垃圾回收器

1、垃圾回收算法

【1】標記-清除算法(Mark-Sweep)

垃圾回收器先判定出所有需要回收的對象,並標記,然後再執行清理工作,把剛纔標記出來的對象都回收掉

缺點:一是效率不高,二是會產生內存碎片

【2】複製算法(Copying)

將經過垃圾回收器工作後的,仍然存活的對象,從執行垃圾回收工作的內存區域,複製到另外一塊空閒的內存區域當中

優點:不會產生內存碎片

缺點:浪費內存區域

【3】標記-整理算法(Mark-Compact)

垃圾回收器先判定出所有需要回收的對象,並標記,然後將不需要回收的對象(未被標記的)向內存空閒的一端移動,最後執行垃圾回收操作

優點:不會產生內存碎片

缺點:效率不高

【4】分代收集算法(Generational Collection)

根據對象的存活週期的不同,將內存劃分爲幾塊,一般是把Java堆分爲新生代和老年代。這樣就可以根據各個年代的特點採用最適合的收集算法

新生代,對象的存活週期較短,適合使用複製算法

老年代,對象的存活週期較長,沒有額外空間對其進行分配擔保,所以適合使用標記-清除標記-整理算法

2、Java垃圾收集器

【1】Serial

串行收集器

在新生代和老年代均有且只有一個收集線程執行垃圾回收操作。在收集線程進行垃圾回收時,必須暫停其他的工作線程,直到它收集結束(STW,Stop The World)

新生代採用單線程工作,使用複製算法;老年代採用單線程工作,使用標記-整理算法

【2】ParNew

新生代並行收集器

它和SerialGC相比,它優化點在於新生代採用並行收集線程來工作。它默認開啓的收集線程數和CPU的數量是一致的,可以通過-XX:ParallelGCThreads參數來限制並行收集的線程數量

新生代採用並行收集線程工作,使用複製算法;老年代採用單線程工作,使用標記-整理算法

【3】Parallel Scavenge

新生代並行收集器

和ParNew相比,它的目標是減少STW的時間,達到一個可控制的吞吐量(Throughput)

吞吐量就是CPU用於執行用戶代碼的時間與CPU總的消耗時間(執行用戶代碼的時間 + GC時間)的比值。比如虛擬機運行了100分鐘,其中垃圾回收消耗1分鐘,則吞吐量就是 (100 - 1) / 100 * 100% = 99%

可以通過-XX:MaxGCPauseMillis=???來控制最大停頓時間,

以及通過-XX:GCTimeRatio=???來控制吞吐量的大小

此外還有一個參數-XX:+UseAdaptiveSizePolicy,打開它後,JVM可以根據當前系統的運行情況,收集性能監控信息,動態地調整諸如:-Xmn(新生代大小)、-XX:SurvivorRatio(伊甸園區和倖存區的比例)和-XX:PretenureSizeThreshold(晉升老年代對象年齡)等參數

新生代採用並行收集線程工作,使用複製算法;老年代採用單線程工作,使用標記-整理算法

【4】Serial Old(PS MarkSweep)

老年代串行收集器

現在主要用於給CMS收集器做後備預案使用,即在發生Concurrent Mode Failure時使用

它在新生代和老年代僅使用一個收集線程來執行垃圾回收操作

Parallel Scavenge的老年代收集器使用PS MarkSweep,但是其實現和Serial Old的實現非常接近,所以在官方的很多資料中,都是用Serial Old來代替PS MarkSweep來進行講解的

新生代採用單線程工作,使用複製算法;老年代採用單線程工作,使用標記-整理算法

【5】Parallel Old

並行收集器

新生代採用並行收集線程工作,使用複製算法;老年代採用並行收集線程工作,使用標記-整理算法

【6】CMS(Concurrent Mark Sweep)

併發標記-清理收集器

它是一種以獲取最短STW時間爲目標的收集器

老年代採用並行標記線程和並行收集線程工作,使用標記-清除算法

工作過程:

  1. 初始標記(CMS initial mark)【STW】

    標記出GC Roots能直接關聯的對象

  2. 併發標記(CMS concurrent mark)【和用戶線程一起工作】

    進行GC Roots Tracing

  3. 重新標記(CMS remark)【STW】

    修正併發標記期間,因用戶程序繼續運作,而導致標記產生變動的,那一部分對象的標記記錄

    這個階段的執行時間大於初始標記的執行時間,小於併發標記的執行時間

  4. 併發清除(CMS concurrent sweep)【和用戶線程一起工作】

缺點:

  1. CMS對CPU資源非常敏感。其實面向併發設計的程序對CPU資源都很敏感。雖說在併發標記階段,它不會STW,但是會因爲佔用了一部分線程(CPU資源),而導致程序變慢,降低總的吞吐量

    默認啓動的回收線程數是 (CPU數量 + 3) / 4,當CPU數量是4個以上時,並行回收垃圾的收集線程不少於25%,而當CPU數量不足4個時,則需要拿出一半的運算能力,給CMS去執行收集線程

  2. CMS無法清理浮動垃圾(Floating Garbage),可能出現Concurrent Mode Failure,而導致Full GC產生

    浮動垃圾就是指在併發標記階段之後出現的新的垃圾對象,這些對象只能等到下一次GC的時候被回收掉

  3. CMS無法像其他收集器一樣,等老年代幾乎滿了,才進行收集。它需要預留一部分空間給並行執行的程序運作時使用。可以通過-XX:CMSInitiatingOccupancyFraction來調整觸發百分比。該屬性默認值:JDK5是68,JDK6是92,JDK7是-1(JDK7還有另外一個參數CMSInitiatingPermOccupancyFraction,對應的是永久代的回收觸發內存比例),JDK8是-1

    如果預留的內存空間無法滿足程序的需要,就會出現Concurrent Mode Failure,這時JVM將啓動後備預案,臨時啓動Serial Old來執行垃圾回收。但是這樣會加長STW的時間。所以預留的內存空間不宜過小

  4. CMS使用標記-清除算法,就會產生內存碎片。內存碎片過多,會給大對象的分配造成很大麻煩。雖然老年代有很多剩餘空間,但是由於無法找到足夠的連續的內存空間,就會觸發Full GC

    有一個參數-XX:+UseCMSCompactAtFullCollection,用於控制當Full GC時,對內存進行整理操作。但是內存整理操作是無法並行的。還有一個參數-XX:CMSFullGCsBeforeCompaction,用於設置當執行多少次沒有進行內存整理的Full GC後,緊接着來一次壓縮的Full GC,默認爲0,即每次Full GC都壓縮

【7】G1

Garbage-First,是一款面向服務端應用的垃圾收集器

G1將整個Java堆劃分爲多個大小相等的區域(Region)。和以往不同的是,雖然它保留了新生代和養老代的概念,但是新生代和養老代卻不再是物理上連續的了,而是每個Region當中都包含新生代和養老代的一部分

默認將Java堆分爲2048左右個Region(如果數量太少,會影響收集效率,增加掃描的時間)。可以通過參數-XX:G1HeapRegionSize=???來調整每個分區的大小,默認爲1MB(1048576B),最大32MB(33554432B),且必須是2的冪次方,不足1MB,取1MB,大於1MB,向下取2的冪次方值

G1還可以有計劃地避免在整個Java堆中進行全Region的垃圾收集,它會跟蹤各個Region裏的垃圾堆積的價值大小(GC可以釋放的內存大小和所需要的收集時間),在後臺維護一個優先級列表,每次根據允許的收集時間,優先回收價值最大的Region(Garbage-First名稱的由來)

特點:

  1. 並行與併發

    充分利用多核CPU的硬件優勢,減少STW的時間,提高吞吐量。並且垃圾收集線程和用戶線程可以併發處理

  2. 分代收集

    G1將每個Region邏輯劃分爲Eden、Survivor和Old。每個Region會隨着G1收集器的運行,而不斷地調整切換

  3. 空間整合

    G1從整體上看,是使用標記-整理算法,但從局部看,是基於複製算法實現的。這就意味着在G1收集器運作期間,不會產生內存碎片。不會因爲程序需要分配大對象時,找不到連續的內存空間,而提前觸發下一次GC

  4. 可預測的停頓

    可以通過參數-XX:MaxGCPauseMillis來指定最大STW時間。並根據制定的回收優先級列表,優先回收那些回收價值大的Region

工作過程:

  1. 初始標記(initial Marking)【STW】

    標記GC Roots能直接關聯到的對象。執行時間較短

  2. 併發標記(Concurrent Marking)【和用戶線程一起工作】

    從GC Roots開始,對堆內存中的對象進行可達性分析,找出存活的對象。執行時間較長,但可以和用戶程序併發執行

  3. 最終標記(Final Marking)【STW】

    修正因程序併發執行而導致標記發生變動的記錄。可以和用戶程序並行執行

  4. 篩選回收(Live Data Counting and Evacuation)

    根據用戶指定的停頓時間,制定收集計劃,並執行收集命令。可以和用戶程序並行執行

注意:就目前而言,G1收集器並不穩定,不推薦在生產系統上使用它

3、垃圾收集器的選擇

【1】JDK源碼

bool Arguments::check_gc_consistency() {
    bool status = true;
    
    uint i = 0;
    if (UseSerialGC)                        i++;
    if (UseConcMarkSweepGC || UseParNewGC)  i++;
    if (UseParallelGC || UseParallelOldGC)  i++;
    if (UseG1GC)                            i++;
    if (i > 1) {
        jio_fprintf(defaultStream::error_stream(),
                   "Conflicting collector combinations in option list; "
                    "please refer to the release notes for the combinations "
                    "allowed\n");
        status = false;
    }

    return status;
}

在JDK的C++源碼中,有個方法用於檢查GC策略配置的正確性

【2】Serial + Serial Old

使用參數-XX:+UseSerialGC

新生代使用串行收集器,老年代也使用串行收集器

【3】ParNew + Serial Old

使用參數-XX:+UseParNewGC

新生代使用並行收集器,老年代使用串行收集器

注意使用該策略配置,虛擬機會拋出警告信息

Java HotSpot(TM) 64-Bit Server VM warning: Using the ParNew young collector with the Serial old collector is deprecated and will likely be removed in a future release

建議不要使用該策略配置,未來發行版本可能會移除它

【4】ParNew + (CMS + Serial Old)

使用參數-XX:+UseConcMarkSweepGC

新生代使用並行收集器,老年代使用並行-清除收集器(串行收集器做後備預案)

【5】Parallel Scavenge + Parallel Old

使用參數-XX:+UseParallelGC-XX:+UseParallelOldGC

新生代使用並行收集器,老年代也使用並行收集器

JDK8默認GC策略配置

【6】G1

使用參數-XX+:UseG1GC

【7】總結

單核:Serial

多核,大吞吐量:Parallel Scavenge 和 Parallel Old

多核,快速響應:ParNew 和 CMS

4、Java對象的內存分配

  1. 優先在Eden(新生代的伊甸園區)進行分配

  2. 大對象直接進入老年代。所謂的大對象就是指需要大量連續的內存空間的對象,例如很長的字符串或大數組。可以通過-XX:PretenureSizeThreshold=???參數來調節對象大小的閾值,默認爲0,即不限制。這個參數不帶單位。注意該參數僅對Serial和ParNew兩種收集器有效,Parallel Scavenge收集器不認識該參數

  3. 對象的存活次數達到MaxTenuringThreshold時,將進入老年代

  4. 如果倖存區中相同年齡的所有對象的大小總和大於Survivor(倖存區域)的一半,則年齡大於或等於該年齡的對象會在Minor GC時被複制到老年代。這就是動態對象年齡判斷

  5. 當Minor GC發生時,如果伊甸園區和某個倖存區中存活的對象,無法複製到另外一個倖存區時,就會通過空間分配擔保機制,向老年代借用內存,提前將對象複製到老年代中

    可以通過XX:+HandlePromotionFailure參數來允許擔保失敗

    如果允許擔保失敗,將會檢查老年代最大可用連續內存是否大於晉升至老年代的對象的平均大小,如果大於,將會嘗試進行Minor GC,否則就進行Full GC。這裏進行Minor GC是存在風險的,因爲可能存在存活的大對象,導致其晉升至老年代,而老年代沒有足夠的內存空間,從而導致Full GC

    如果不允許擔保失敗,則直接進行Full GC

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