JVM學習筆記(4)---垃圾收集器

markriver發表於2021-09-09

哪些記憶體需要回收?

垃圾收集(Garbage Collection,GC),即常說的GC。
儘管目前“自動化”時代,記憶體由JVM自動回收,但需要了解其回收機制。當出現記憶體溢位、洩漏問題時方便排查,當垃圾收整合為系統達到更高併發量的瓶頸時,方便監控調節
由於程式計數器、虛擬機器棧、本地方法棧隨執行緒生滅,且每個棧幀分配多少記憶體都是在類結構確定下來就已知,所以這幾個區域的記憶體分配和回收都有確定性
而堆和方法區的記憶體分配是 動態 的,這裡所討論的GC即指 堆和方法區 的記憶體。

什麼時候回收?

記憶體的回收時機主要在於確定 物件是否還透過任何途徑被使用

引用計數演算法(已經淘汰的演算法)

引用計數演算法是給每個物件新增一個引用計數器,每當一個地方引用它,計數器就加一;引用失效時,計數器就減一。
其特點是:演算法很簡單,判定效率高。但它不能解決相互引用的問題。
看如下示例程式碼:

public class ReferenceCountingGC {    public Object instance = null;    //這個成員屬性單純用於記憶體佔用,方便檢視是否回收
    private byte[] bigSize = new byte[2*1024*1024];    public static void testGC(){
        ReferenceCountingGC objA = new ReferenceCountingGC();
        ReferenceCountingGC objB = new ReferenceCountingGC();

        objA.instance = objB;
        objB.instance = objA;        //清除引用
        objA = null;
        objB = null;        //GC 觀察objA與objB能否被回收
        System.gc();
    }    public static void main(String[] args) {

      testGC();

    }
}

物件ObjA與ObjB互相引用,但再無其他引用。如果採用引用計數演算法,GC就無法回收他們。
主流的JVM中,沒有采用引用計數法,就是因為它無法解決物件間迴圈引用的問題。

執行上段程式碼,我們可以看到記憶體 4328k->644k 的變化,說明對ObjA和ObjB的記憶體進行了回收,也說明Java虛擬機器中沒有采用引用計數演算法。

圖片描述

執行結果


可達性分析演算法

主流商用程式語言(Java,C#等)中,都是透過 可達性分析(Rechability Analysis) 判斷物件存活。

圖片描述

可達性分析


可達性分析是透過一系列"GC Roots"作為起始點,向下開始搜尋,所經過的路徑稱為引用鏈(Reference Chain),當一個物件到GC Roots沒有任何鏈相連,則物件不可用。


Java中,可作為GC Roots的物件包括以下幾種:

虛擬機器棧中引用的物件    (棧幀中的區域性變數表)
本地方法棧中JNI引用的物件    (JNI即Native方法)
方法區中靜態屬性引用的物件    (static)
方法區中常量引用的物件    (final)

物件引用

判斷物件的存活,無論是引用計數法計算數量,還是可達性分析法判斷引用的可達性,都與 引用 有關。

引用可分為 4 類:

  • 強引用(Strong Reference):形如 Object obj = new Object(),只要引用在,GC不回收物件。

  • 軟引用(SoftReference):記憶體溢位前,會進行二次回收,適合做快取

  • 弱引用(WeakReference):下一次GC前,會直接進行回收

  • 虛引用(PhantomRefence):僅用於物件回收時傳送系統通知


強引用
強引用的物件,不會被GC回收;刪除強引用後,GC才會回收。
示例程式碼如下:

/**
 * 強引用:GC不會回收物件
 */public class StrongRef {    public static void main(String[] args) throws InterruptedException {
        Referred strong = new Referred();
        System.gc();
        Thread.sleep(2000);        //刪去引用
        strong = null;
        System.out.println("刪除強引用後");
        System.gc();
        Thread.sleep(2000);
    }    static class Referred {        @Override
        protected void finalize() throws Throwable {
            System.out.println("GC時引用物件被收集");
        }
    }
}

finalize()函式:如果一個物件覆蓋了finalize()函式,則在物件被回收時,finalize()方法會被GC收集器觸發。每個物件的finalize()函式只會被系統呼叫一次。

執行結果如下:

刪除強引用後
GC時引用物件被收集

當強引用存在時,GC時物件沒被回收;當強引用被刪除,GC時物件被回收。


軟引用
軟引用的物件,在記憶體溢位前,會進行二次回收,這種特性非常適合做快取。

示例程式碼如下:

/**
 * VM args: -Xmx100m -Xms100m
 *
 */public class SoftRef {    public static void main(String[] args) throws InterruptedException {

        SoftReference<Referred> soft = new SoftReference<Referred>(new Referred());

        System.gc();
        Thread.sleep(2000);

        System.out.println("開始堆佔用");        try {
            List<SoftRef> heap = new ArrayList<>();            while (true) {
                heap.add(new SoftRef());
            }
        } catch (OutOfMemoryError e) {            // 軟引用物件應該在這個之前被收集
            System.out.println("記憶體溢位");
        }
    }    static class Referred {        @Override
        protected void finalize() throws Throwable {
            System.out.println("GC時引用物件被收集");
        }
    }
}



作者:weberweber
連結:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/818/viewspace-2818122/,如需轉載,請註明出處,否則將追究法律責任。

相關文章