JVM垃圾回收演算法

1 什麼時候回收垃圾?

1、什麼場景下該使用什麼垃圾回收策略?

在對記憶體要求苛刻的場景:想辦法提高對象的回收效率,多回收掉一些對象,騰出更多記憶體。

在CPU使用率高的情況下:降低高並發時垃圾回收的頻率,讓CPU更多的去執行你的業務而不是垃圾回收。

2、垃圾回收發生在哪些區域?

堆(回收對象)、方法區(不用的常量、類)

3、對象什麼時候被回收?

引用計數法:通過對象的引用計數器來判斷對象是否被引用。無法處理循環引用問題。

A --> B --> C --> D --> B 此時B被引用了兩次
當A不再引用B
A     B --> C --> D --> B BCD循環引用無法被回收。

可達性分析法:以根對象(GC Roots)為起點向下搜索,形成引用鏈。如果對象不在引用鏈上,便視作對象不可達,可以回收。

image

哪些對象可以作為GC Roots對象?

  • 虛擬機棧(棧幀中的本地變數表)中引用的對象
  • 方法區中類靜態屬性引用的對象
  • 方法區中常量引用的對象
  • 本地方法棧中JNI(即 Native方法)引用的對象

注意的是,一個對象不可達,也不一定會被回收。

image

public class GCTest {
    private static GCTest obj;

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize方法被調用了");
        obj = this; // 重新引用對象,從FQ隊列中出隊
    }

    public static void main(String[] args) throws InterruptedException {
        obj = new GCTest();
        obj = null;
        System.gc();

        TimeUnit.MILLISECONDS.sleep(1000);
        extracted();

        TimeUnit.MILLISECONDS.sleep(1000);
        obj = null; // finalize()方法已被調用過,對象會被直接回收
        System.gc();
        extracted();
    }

    private static void extracted() {
        if (obj == null) {
            System.out.println("obj == null");
        } else {
            System.out.println("obj可用");
        }
    }
}
finalize方法被調用了
obj可用
obj == null

finalize()的建議

  • 避免使用finalize()方法,操作不當可能會導致問題。上面的例子中,第二次回收時如果不設置obj==null,對象很難被回收掉。

  • finalize()優先順序低,何時會被調用無法確定,因為什麼時間發生GC不確定。

  • 建議使用try…catch…finally來替代finalize()。

4、四種引用

強引用,例如new對象,永遠不會回收對象。

軟引用,描述有用但是非必需對象,只有記憶體不足時被回收。

弱引用,描述非必需對象,無論記憶體情況都會被回收。

虛引用,主要用來跟蹤對象被垃圾回收器回收的活動,結合引用隊列使用。不影響對象的生命周期。

Object obj = new Object();
SoftReference<String> sr = new SoftReference<>("helllo");
WeakReference<String> sr = new WeakReference<>("helllo");
ReferenceQueue< String> queue = new ReferenceQueue<>();
PhantomReference< String> pr= new PhantomReference<>(「hello「queue)

2 垃圾回收演算法

垃圾回收演算法

基礎垃圾回收演算法

  • 標記清除法(Mark-Sweep):標記後直接刪除。

    • 容易產生記憶體碎片;分配大記憶體時速度受到影響,需要找到適合它大小的空間。
  • 標記整理法(Mark-Compact):標記,記憶體整理到一側,刪除三步。

    • 整理存在開銷,沒有碎片。
  • 複製(Copy):記憶體分為兩塊,每次只使用其中一塊;將存活對象複製到另一塊記憶體,然後清空當前記憶體;交換使用另一塊記憶體。

    • 記憶體利用率低,性能好,沒有碎片。

綜合垃圾回收演算法

  • 分代回收演算法:把記憶體分為多個區域,不同區域使用不同的回收演算法。
  • 增量演算法:每次只收集一小塊記憶體區域的垃圾。

分代收集演算法

商業虛擬機普遍採用分代收集演算法,根據對象的存活周期,把記憶體分為多個區域,不同區域使用不同的回收演算法。

分代的好處是更有效的清除不再需要的對象,提升垃圾回收的效率。

  • 新生代回收(Minor GC | Young GC)
  • 老年代回收(Major GC),通常伴隨Full GC,Major GC≈ Full GC
  • 清理整個堆(Full GC)

典型的對象分配流程如下。
image

新生代採用的是複製演算法,存放存活周期短的對象。老年代對象存活時間長,採用標記-清除或標記-整理演算法。

堆的年輕代和老年代,記憶體比例1:2。年輕代分為Eden和Survivor,記憶體比例8:1:1。
1. 對象首先存放到Eden中,當Eden記憶體滿後,垃圾收集執行緒執行minor gc,通過GC Root進行可達性分析,找到非垃圾對象,複製到Survivor S0區(對象頭中分代年齡標記為1),清空Eden區域,Eden又可以存儲新對象。
2. 當Eden再次滿後,同時對Eden和S0執行minor gc,將非垃圾對象複製到S1(分代年齡加1),清空Eden和S0。S0和S1,同一時間只有一個被使用。
3. 當分代年齡達到15後,對象會被複制到老年代。
4. 當老年代滿了,會對年輕代和老年代執行full GC。full GC不起作用,則會OOM。

命令行輸入jvisualvm,打開Java VisualVM工具的Visual GC插件可以看到程式執行過程中堆容量變化。每次Eden滿進行GC,調整堆區域。

image

非典型的情況:

  1. 新建的對象不一定分配到伊甸園。
  • 對象大於-XX:PretenureSizeThreshold,就會直接分配到老年代

  • 新生代空間不夠。例如大數組。

  1. 對象也不一定要達到年齡才進入老年代

動態年齡:如果Survivor空間中所有相同年齡對象大小的總和大於Survivor空間的一半,那麼年齡大於等於該年齡的對象就可以直接進入老年代。

觸發垃圾回收的條件

Minor GC觸發條件:伊甸園空間不足

Full GC觸發條件:

  1. 老年代空間不足(老年代已滿;空間碎片太多,沒有連續記憶體分配對象)
  2. 元空間不足
  3. 要轉成老年代對象所需的空間大於老年代剩餘空間。
  4. 顯示調用System.gc(),建議垃圾回收器執行垃圾回收。設置-XX:+DisableExplicitGC 參數忽略該操作。
分代收集調優思路
  • 讓GC盡量發生在新生代,盡量減少Full GC的發生。
  • 合理設置Survior區域的大小,避免記憶體浪費。(複製演算法)
Tags: