垃圾收集器與記憶體分配策略

溫暖如太陽發表於2022-05-02

程式計數器、虛擬機器棧、本地方法棧三個區域隨著執行緒的建立而建立、執行完成銷燬,棧中的棧幀隨著放大的進入和退出執行入棧與出棧,每個棧幀分配多少記憶體基本上是在類結構確定下來時已知,因此這幾個區域的記憶體分配與回收都具備確定性。
Java堆中存放的所有物件的例項,只有在程式執行期間我們才會知道會建立哪些物件,這部分記憶體分配與回收都是動態的,垃圾收集器重點關注的就是這部分。

引入計數算數
給物件中新增一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減1;任何時刻計數器都為0的的物件就是不可能再被使用的。
缺點:它很難解決物件之間的相互迴圈引用的問題。

VM ages:-XX:+PrintGCDetails 列印GC詳細資訊:

package memory;

public class ReferenceCountingGC {

    public Object instance = null;

    private static  final  int _1MB = 1024 *1024;

    private byte[] bigSize = new byte[2 *_1MB];

    public static  void  main(String[] arg){
        ReferenceCountingGC objA = new ReferenceCountingGC();
        ReferenceCountingGC objB = new ReferenceCountingGC();
        objA.instance = objB;
        objB.instance = objA;

        System.gc();
    }
}

日誌:

[15.841s][info   ][gc,start    ] GC(0) Pause Full (System.gc())
[15.842s][info   ][gc,task     ] GC(0) Using 3 workers of 8 for full compaction
[15.843s][info   ][gc,phases,start] GC(0) Phase 1: Mark live objects
[15.847s][info   ][gc,phases      ] GC(0) Phase 1: Mark live objects 4.412ms
[15.848s][info   ][gc,phases,start] GC(0) Phase 2: Prepare for compaction
[15.849s][info   ][gc,phases      ] GC(0) Phase 2: Prepare for compaction 1.244ms
[15.849s][info   ][gc,phases,start] GC(0) Phase 3: Adjust pointers
[15.851s][info   ][gc,phases      ] GC(0) Phase 3: Adjust pointers 1.951ms
[15.851s][info   ][gc,phases,start] GC(0) Phase 4: Compact heap
[15.853s][info   ][gc,phases      ] GC(0) Phase 4: Compact heap 1.987ms
[15.857s][info   ][gc,heap        ] GC(0) Eden regions: 4->0(9)
[15.857s][info   ][gc,heap        ] GC(0) Survivor regions: 0->0(0)
[15.857s][info   ][gc,heap        ] GC(0) Old regions: 0->3
[15.857s][info   ][gc,heap        ] GC(0) Archive regions: 0->0
[15.857s][info   ][gc,heap        ] GC(0) Humongous regions: 6->6
[15.857s][info   ][gc,metaspace   ] GC(0) Metaspace: 424K(640K)->424K(640K) NonClass: 399K(512K)->399K(512K) Class: 24K(128K)->24K(128K)
[15.857s][info   ][gc             ] GC(0) Pause Full (System.gc()) 9M->6M(30M) 15.980ms
[15.857s][info   ][gc,cpu         ] GC(0) User=0.02s Sys=0.02s Real=0.02s
[17.684s][info   ][gc,heap,exit   ] Heap
[17.684s][info   ][gc,heap,exit   ]  garbage-first heap   total 30720K, used 7088K [0x0000000081800000, 0x0000000100000000)
[17.684s][info   ][gc,heap,exit   ]   region size 1024K, 1 young (1024K), 0 survivors (0K)
[17.684s][info   ][gc,heap,exit   ]  Metaspace       used 425K, committed 640K, reserved 1114112K
[17.684s][info   ][gc,heap,exit   ]   class space    used 24K, committed 128K, reserved 1048576K

從日誌看出,記憶體進行了回收,說明JVM 的GC使用的不是引用計數演算法。

根搜尋演算法
通過一系列的名為 “GC Roots”的物件作為起始點,從這些節點開始向下搜尋,搜尋所走過的路徑稱為引用鏈(Reference Chain),當一個物件的GC Roots 沒有任何引用鏈相連時,則證明此物件是不可用的。

引用
引用分為:

  • 強引用(Strong Reference) :只要強引用還在,垃圾收集器永遠不會回收掉引用的物件。
  • 軟引用(Soft Reference):在系統將要發生記憶體溢位異常之前,將會把這些物件列進回收範圍之中並進行第二次回收。如果這次回收還沒有足夠的記憶體,才會丟擲記憶體溢位異常。
  • 弱引用(Weak Reference):被弱引用關聯的物件只能生存到下一次垃圾收集發生之前。當垃圾收集器工作時,無論當前記憶體是否足夠,都會回收掉只被弱引用關聯的物件。
  • 虛引用(Phantom Reference)(幽靈引用、幻影引用):一個物件是否有虛引用的存在,完全不會對其生存時間構成影響,也無法通過虛引用來取得一個物件例項。為一個物件設定虛引用關聯的唯一目的就是希望能在這個物件被收集器回收時收到一個系統通知。

在跟搜尋演算法中不可達的物件,至少要經歷兩次標記過程:如果物件在進行根搜尋後發現沒有與GC Roots相連的引用鏈,那它將會被第一次標記並進行一次篩選,篩選條件是此物件是否有必要執行finalize()方法。當物件沒有覆蓋finalize()方法,或者finalize()方法已經被虛擬機器呼叫過,虛擬機器將這兩種情況視為“沒有必要執行”。
如果物件有必要執行finalize()方法,那麼這個物件將被放置在一個F-Queue的佇列之中由Finalizer執行緒(虛擬機器建立並出發)執行。finalizer用於告訴垃圾回收器下一步應該執行的操作。然後,GC將對F-Queue中的物件進行二次小規模的標記。

package memory;

public class FinalizeEscapeGC {

    public static  FinalizeEscapeGC SAVE_HOOK = null;

    public  void  isAlive(){
        System.out.println("yes, i am still alive;");
    }
    @Override
    protected void finalize() throws Throwable{
        super.finalize();
        System.out.println("fialize mehtod executed!");
        //重新引用
        FinalizeEscapeGC.SAVE_HOOK = this;
    }

    public static void main(String[] args) throws Throwable{
        SAVE_HOOK = new FinalizeEscapeGC();
        //物件第一次成功拯救自己
        SAVE_HOOK = null;
        System.gc();
        //因為Finalizer方法優先順序很低,暫停500毫秒,等它執行
        Thread.sleep(500);

        if(SAVE_HOOK != null){
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("no ,i am dead");
        }
        //下面這段程式碼與上面的完全相同,但是這次自救失敗~
        SAVE_HOOK = null;

        System.gc();

        Thread.sleep(500);

        if(SAVE_HOOK != null){
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("no ,i am dead !!!");
        }
    }
}

執行結果:

fialize mehtod executed!
yes, i am still alive;
no ,i am dead !!!

任何一個物件的finalize()方法都只會被系統自動呼叫一次,如果物件面臨下一次回收,他的finalize()方法不會被再次執行。
finalize()能做的所有工作,使用try-finally或者其他方式都可以做得更好、更及時。

回收方法區

  • 回收的主要內容為:廢棄常量和無用的類
  • 廢棄常量:沒有任何物件引用常量池中的常量,也沒有其他地方引用這個字面量。
  • 無用類判定條件:
  1. 該類所有的例項都已經被回收,也就是Java堆中不存在該類的任何例項。
  2. 載入該類的ClassLoader已經被回收。
  3. 該類對應的java.lang.Class物件沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。

垃圾收集演算法


標記-清除演算法
首先標記出所有需要回收的物件,在標記完成後同意回收掉所有被標記的物件。
缺點:

  • 一個是效率問題,標記和清除過程的效率都不高;
  • 另一個是空間問題,標記清除之後會產生大量不連續的記憶體碎片,空間碎片太多可能會導致程式在需要分配較大物件時無法找到連續的記憶體,而不得不提前觸發另一次垃圾收集動作。

複製演算法
將可用記憶體按容量劃分為大小相等的兩塊,每次只使用其中的一塊,當這一塊記憶體用完後,就將還存活的物件複製到另外一塊上面,然後再把已使用過的記憶體空間一次清理掉。
缺點:將記憶體縮小為原來的一半。
現在商業虛擬機器都採用這種收機演算法來回收新生代,將記憶體分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中的一塊Survivor。當回收時,將Eden和Survivor中還存活的物件一次性拷貝到另外一塊Survivor空間上,最後清理掉Eden和剛才用過的Survivor空間。
這裡有個問題,無法保證回收後存活物件一塊Survivor空間夠用,所以這裡需要依賴其他記憶體(老年代)進行分配擔保。

標記-整理演算法
根據老年代的特點,對存活物件進行標記,讓所有存活物件向一端移動,然後直接清理掉端邊以外的記憶體。

分代收機演算法
根據物件噸貨週期的不同將記憶體劃分為幾塊,JAVA一般將堆分為新生代和老生代,這樣就可以根據各個年代的特點採用最適當的收集演算法。

垃圾收集器
Serial收集器
是最基本、歷史最悠久的收集器,是一個單執行緒收集器,在進行垃圾收集的時候,會暫停掉其他的所有工作執行緒。(簡單而高效)

ParNew收集器
Serial收集器的多執行緒版,除了使用多執行緒進行垃圾收集外,其餘的與Serial收集器一致。關注點是儘可能縮短使用者執行緒停頓時間。

Parallel Scavenge 收集器
新生代收集器,他也是使用複製演算法的收集器,也是並行的多執行緒收集器。目標是達到一個可控制的吞吐量。自適應調節策略。
吞吐量 = 執行使用者程式碼的時間/(執行使用者程式碼時間+垃圾收集時間)
停頓時間越短就越需要與使用者互動的程式,良好的響應速度能提升使用者體驗;高吞吐量則可以更高效率地利用CPU時間,儘快完成程式的運算任務,主要適用於後臺運算而不需要太多互動的任務。

Serial Old收集器
Serial Old是Serial收集器的老年代版本,使用“標記-整理”演算法。

Parallel Old收集器
是Parallel Scavenge收集器的老年代版本,使用多執行緒和“標記-整理”演算法。

CMS收集器
CMS收集器是一種以獲取最短回收停頓時間為目標的收集器。使用“標記-清除”演算法實現。
步驟:

  • 初始標記
  • 併發標記
  • 重新標記
  • 併發清除
  • 其中初始標記、重新標記兩個步驟需要使用者工作執行緒暫停,初始標記只標記GC Roots能直接關聯到的物件,併發標記階段進行 GC Root Tracing 過程,重新標記階段則是為修正併發標記期間,使用者程式繼續執行而導致標記變化產生變動的那部分物件的標記記錄。
  • 缺點:
  • CMS收集器對CPU資源非常敏感。CMS預設用的回收執行緒數(CPU數量+3)/4。為解決該問題,虛擬機器提供了一種i-CMS(增量式併發收集器)的CMS收集器變種,工作方式就是在併發標記和併發清理的時候讓GC執行緒與使用者執行緒交替執行,儘量減少GC執行緒獨佔資源的時間,這樣整個垃圾收集的過程會更長,但對使用者程式的影響就會顯得少一些。
  • CMS收集器無法處理浮動垃圾。如果CMS執行期預留的記憶體無法滿足程式需要,就會出現一次“Concurrent Mode Failure”失敗,這時候虛擬機器將啟動後備元:臨時啟動Serial Old 收集器重新進行老年代的垃圾收集。
  • CMS是一款基於“標記-清除”演算法實現的收集器,收集結束時會產生大量的空間碎片。

G1 收集器

  • G1收集器是基於“標記-整理”演算法實現的收集器,也就是說它不會產生空間碎片,這對於長時間執行的應用系統來說非常重要。
  • 它可以非常精準地控制停頓,既能讓使用者明確指定在一個長度為M毫秒的時間片段,消耗在垃圾收集上的時間不得超過N毫秒。

G1將正好Java堆(包括新生代、老年代)劃分為多個大小固定的獨立區域,並且跟蹤這些區域裡面的垃圾堆積程度,在後臺維護一個優先列表,每次根據允許的收集時間,有限回抽垃圾最多的區域。

記憶體分配和回收策略

物件優先在Eden分配

多數情況下,物件在新生代Eden區中扽配,當Eden區域沒有足夠的空間分配時,虛擬機器將發起一次Minor GC。

package memory;

public class JavaEdenTest {

    private static  final  int _1MB = 1024 *1024;

    public  static  void  testAllocation(){
        byte[] allocation1,allocation2,allocation3,allocation4;
        allocation1 = new byte[2 *_1MB];
        allocation2 = new byte[2 * _1MB];
        allocation3 = new  byte[2 *_1MB];
        allocation4 = new  byte[4 *_1MB];
    }

    /**
     * -verbose:gc
     * -Xms20M -Xmx20M -Xmn10M   顯示JAVA堆的大小20M且不可擴充套件
     * 其中10M分配給新生代,剩餘10M分配給老年代
     * -XX:SurvivorRatio=8 確定新生代中的Eden區與一個Survivor區域的空間比例為8:1
     * -XX:+PrintGCDetails 列印詳細的收集器日誌引數
     *
     * @param args
     */
    public  static  void  main(String[] args){
        testAllocation();
    }
}

執行日誌:

[16.630s][info   ][gc,start    ] GC(0) Pause Young (Concurrent Start) (G1 Humongous Allocation)
[16.631s][info   ][gc,task     ] GC(0) Using 2 workers of 8 for evacuation
[16.639s][info   ][gc,phases   ] GC(0)   Pre Evacuate Collection Set: 0.3ms
[16.639s][info   ][gc,phases   ] GC(0)   Merge Heap Roots: 0.1ms
[16.639s][info   ][gc,phases   ] GC(0)   Evacuate Collection Set: 4.8ms
[16.639s][info   ][gc,phases   ] GC(0)   Post Evacuate Collection Set: 2.1ms
[16.639s][info   ][gc,phases   ] GC(0)   Other: 0.8ms
[16.639s][info   ][gc,heap     ] GC(0) Eden regions: 3->0(9)
[16.639s][info   ][gc,heap     ] GC(0) Survivor regions: 0->1(2)
[16.639s][info   ][gc,heap     ] GC(0) Old regions: 0->0
[16.639s][info   ][gc,heap     ] GC(0) Archive regions: 0->0
[16.639s][info   ][gc,heap     ] GC(0) Humongous regions: 9->9
[16.639s][info   ][gc,metaspace] GC(0) Metaspace: 419K(576K)->419K(576K) NonClass: 394K(448K)->394K(448K) Class: 24K(128K)->24K(128K)
[16.639s][info   ][gc          ] GC(0) Pause Young (Concurrent Start) (G1 Humongous Allocation) 11M->10M(20M) 9.150ms
[16.639s][info   ][gc,cpu      ] GC(0) User=0.00s Sys=0.00s Real=0.01s
[16.640s][info   ][gc          ] GC(1) Concurrent Undo Cycle
[16.640s][info   ][gc,marking  ] GC(1) Concurrent Cleanup for Next Mark
[16.640s][info   ][gc,marking  ] GC(1) Concurrent Cleanup for Next Mark 0.402ms
[16.640s][info   ][gc          ] GC(1) Concurrent Undo Cycle 0.770ms

allocation4在例項化時,出發了一次Minor GC。

大物件直接進入老年代
VM引數:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:+PrintGCDetails -XX:PretenureSizeThreshold=3145728
-XX:PretenureSizeThreshold的作用是令大於這個設定值的物件直接進入老年代中分配。
避免在Eden區及兩個Survivor區之間發生大量的記憶體拷貝。

package memory;

public class JavaTestBigObjectGC {

    private static  final int _1MB = 1024 * 1024;

    public  static  void  main(String[] args){
        byte[] allocation;
        allocation = new byte[4 * _1MB];
    }
}

執行日誌:

[5.942s][info   ][gc,heap,exit] Heap
[5.942s][info   ][gc,heap,exit]  garbage-first heap   total 20480K, used 7586K [0x00000000fec00000, 0x0000000100000000)
[5.942s][info   ][gc,heap,exit]   region size 1024K, 3 young (3072K), 0 survivors (0K)
[5.942s][info   ][gc,heap,exit]  Metaspace       used 419K, committed 576K, reserved 1114112K
[5.942s][info   ][gc,heap,exit]   class space    used 24K, committed 128K, reserved 1048576K

長期存活物件進入老年代
虛擬機器給每個物件定義了一個物件年齡計數器,如果物件在Eden中生成,沒經過一次Minor GC後仍然存活,並且能被Survivor容納的話,將會被移動到Survivor空間中,並將物件年齡設定為1,當年齡增加到一定程度(預設為15歲)時,就會被晉升到老年代中。
物件寄生老年的年齡閾值,可以使用-XX:MaxTenuringThreshold來設定。

package memory;

public class JavaTestTenuringThreshold {

    private static  final int _1MB = 1024 *1024;

    /**
     * -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8
     * -XX:MaxTenuringThreshold=1
     */
    public  static  void  testTenuringThreshold(){
        byte[] allocation1,allocation2,allocation3;
        allocation1 = new byte[4 * _1MB];
        allocation2 = new byte[4 * _1MB];
        allocation3 = new byte[4* _1MB];
        allocation3 = null;
        allocation3 = new byte[4*_1MB];

    }

    public  static  void  main(String[] args){
        testTenuringThreshold();
    }
}
[6.680s][info   ][gc,start    ] GC(0) Pause Young (Concurrent Start) (G1 Humongous Allocation)
[6.680s][info   ][gc,task     ] GC(0) Using 2 workers of 8 for evacuation
[6.688s][info   ][gc,phases   ] GC(0)   Pre Evacuate Collection Set: 0.4ms
[6.688s][info   ][gc,phases   ] GC(0)   Merge Heap Roots: 0.2ms
[6.688s][info   ][gc,phases   ] GC(0)   Evacuate Collection Set: 4.8ms
[6.688s][info   ][gc,phases   ] GC(0)   Post Evacuate Collection Set: 1.8ms
[6.688s][info   ][gc,phases   ] GC(0)   Other: 0.7ms
[6.688s][info   ][gc,heap     ] GC(0) Eden regions: 3->0(9)
[6.688s][info   ][gc,heap     ] GC(0) Survivor regions: 0->1(2)
[6.688s][info   ][gc,heap     ] GC(0) Old regions: 0->0
[6.688s][info   ][gc,heap     ] GC(0) Archive regions: 0->0
[6.688s][info   ][gc,heap     ] GC(0) Humongous regions: 5->5
[6.688s][info   ][gc,metaspace] GC(0) Metaspace: 423K(640K)->423K(640K) NonClass: 399K(512K)->399K(512K) Class: 24K(128K)->24K(128K)
[6.688s][info   ][gc          ] GC(0) Pause Young (Concurrent Start) (G1 Humongous Allocation) 7M->6M(20M) 8.803ms
[6.688s][info   ][gc,cpu      ] GC(0) User=0.02s Sys=0.02s Real=0.01s
[6.689s][info   ][gc          ] GC(1) Concurrent Undo Cycle
[6.689s][info   ][gc,marking  ] GC(1) Concurrent Cleanup for Next Mark
[6.689s][info   ][gc,marking  ] GC(1) Concurrent Cleanup for Next Mark 0.187ms
[6.689s][info   ][gc          ] GC(1) Concurrent Undo Cycle 0.494ms
[6.696s][info   ][gc,start    ] GC(2) Pause Young (Concurrent Start) (G1 Humongous Allocation)
[6.696s][info   ][gc,task     ] GC(2) Using 2 workers of 8 for evacuation
[6.699s][info   ][gc,phases   ] GC(2)   Pre Evacuate Collection Set: 0.2ms
[6.699s][info   ][gc,phases   ] GC(2)   Merge Heap Roots: 0.1ms
[6.699s][info   ][gc,phases   ] GC(2)   Evacuate Collection Set: 1.9ms
[6.699s][info   ][gc,phases   ] GC(2)   Post Evacuate Collection Set: 0.5ms
[6.699s][info   ][gc,phases   ] GC(2)   Other: 0.1ms
[6.699s][info   ][gc,heap     ] GC(2) Eden regions: 0->0(10)
[6.699s][info   ][gc,heap     ] GC(2) Survivor regions: 1->0(2)
[6.699s][info   ][gc,heap     ] GC(2) Old regions: 0->1
[6.699s][info   ][gc,heap     ] GC(2) Archive regions: 0->0
[6.699s][info   ][gc,heap     ] GC(2) Humongous regions: 10->10
[6.699s][info   ][gc,metaspace] GC(2) Metaspace: 424K(640K)->424K(640K) NonClass: 399K(512K)->399K(512K) Class: 24K(128K)->24K(128K)
[6.699s][info   ][gc          ] GC(2) Pause Young (Concurrent Start) (G1 Humongous Allocation) 11M->10M(20M) 3.165ms
[6.699s][info   ][gc,cpu      ] GC(2) User=0.02s Sys=0.02s Real=0.00s
[6.699s][info   ][gc          ] GC(3) Concurrent Mark Cycle
[6.699s][info   ][gc,marking  ] GC(3) Concurrent Clear Claimed Marks
[6.699s][info   ][gc,marking  ] GC(3) Concurrent Clear Claimed Marks 0.011ms
[6.699s][info   ][gc,marking  ] GC(3) Concurrent Scan Root Regions
[6.700s][info   ][gc,marking  ] GC(3) Concurrent Scan Root Regions 0.900ms
[6.700s][info   ][gc,marking  ] GC(3) Concurrent Mark
[6.700s][info   ][gc,marking  ] GC(3) Concurrent Mark From Roots
[6.700s][info   ][gc,task     ] GC(3) Using 2 workers of 2 for marking
[6.701s][info   ][gc,marking  ] GC(3) Concurrent Mark From Roots 0.666ms
[6.701s][info   ][gc,marking  ] GC(3) Concurrent Preclean
[6.701s][info   ][gc,marking  ] GC(3) Concurrent Preclean 0.013ms
[6.715s][info   ][gc,start    ] GC(3) Pause Remark
[6.716s][info   ][gc          ] GC(3) Pause Remark 15M->15M(20M) 0.810ms
[6.716s][info   ][gc,cpu      ] GC(3) User=0.00s Sys=0.00s Real=0.00s
[6.716s][info   ][gc,marking  ] GC(3) Concurrent Mark 16.267ms
[6.716s][info   ][gc,marking  ] GC(3) Concurrent Rebuild Remembered Sets
[6.716s][info   ][gc,marking  ] GC(3) Concurrent Rebuild Remembered Sets 0.007ms
[6.717s][info   ][gc,start    ] GC(3) Pause Cleanup
[6.717s][info   ][gc          ] GC(3) Pause Cleanup 15M->15M(20M) 0.019ms
[6.717s][info   ][gc,cpu      ] GC(3) User=0.00s Sys=0.00s Real=0.00s
[6.717s][info   ][gc,marking  ] GC(3) Concurrent Cleanup for Next Mark
[6.717s][info   ][gc,start    ] GC(4) Pause Young (Normal) (G1 Humongous Allocation)
[6.717s][info   ][gc,task     ] GC(4) Using 2 workers of 8 for evacuation
[6.718s][info   ][gc,phases   ] GC(4)   Pre Evacuate Collection Set: 0.1ms
[6.718s][info   ][gc,phases   ] GC(4)   Merge Heap Roots: 0.1ms
[6.718s][info   ][gc,phases   ] GC(4)   Evacuate Collection Set: 0.1ms
[6.718s][info   ][gc,phases   ] GC(4)   Post Evacuate Collection Set: 0.4ms
[6.718s][info   ][gc,phases   ] GC(4)   Other: 0.0ms
[6.718s][info   ][gc,heap     ] GC(4) Eden regions: 0->0(10)
[6.718s][info   ][gc,heap     ] GC(4) Survivor regions: 0->0(2)
[6.718s][info   ][gc,heap     ] GC(4) Old regions: 1->1
[6.718s][info   ][gc,heap     ] GC(4) Archive regions: 0->0
[6.718s][info   ][gc,heap     ] GC(4) Humongous regions: 15->10
[6.718s][info   ][gc,metaspace] GC(4) Metaspace: 424K(640K)->424K(640K) NonClass: 399K(512K)->399K(512K) Class: 24K(128K)->24K(128K)
[6.718s][info   ][gc          ] GC(4) Pause Young (Normal) (G1 Humongous Allocation) 15M->10M(20M) 0.861ms
[6.718s][info   ][gc,cpu      ] GC(4) User=0.00s Sys=0.00s Real=0.00s
[6.718s][info   ][gc,marking  ] GC(3) Concurrent Cleanup for Next Mark 1.440ms
[6.718s][info   ][gc          ] GC(3) Concurrent Mark Cycle 19.012ms

動態物件年齡判定
為了能更好地適應不同程式的記憶體情況,虛擬機器並不總是要求物件的年齡必須達到MaxTenuringshold才能晉升老年代,如果在Survivor空間中相同年齡所有物件大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的物件就可以直接進入老年代。

空間擔保
在發生Minor GC時,虛擬機器會在檢測之前每次晉升到老年代的平均大小是否大於老年代的剩餘空間大小,如果大於,則改為直接進行一次Full GC。如果小於,則檢視HandlePromotionFailure設定是否允許擔保失敗;如果允許,那隻會進行Minor GC;如果不允許,則也要改為進行一次 Full GC。


 

相關文章