天天看點

JVM專題(十)-垃圾回收(一)

文章目錄

  • ​​1.如何判斷對象可以回收​​
  • ​​1.1.引用計數法​​
  • ​​1.2.可達性分析算法​​
  • ​​2.五種引用​​
  • ​​6.代碼示例​​
  • ​​6.1軟引用​​
  • ​​6.2 弱引用​​

1.如何判斷對象可以回收

1.1.引用計數法

  • 當一個對象被其他變量引用,該對象計數加一,當某個變量不在引用該對象,其計數減一
  • 當一個對象引用沒有被其他變量引用時,即計數變為0時,該對象就可以被回收
缺點:循環引用時,兩個對象的計數都為1,導緻兩個對象都無法被釋放
JVM專題(十)-垃圾回收(一)

1.2.可達性分析算法

  • JVM中的垃圾回收器通過可達性分析來探索所有存活的對象
  • 掃描堆中的對象,看能否沿着GC Root對象為起點的引用鍊找到該對象,如果找不到,則表示可以回收
  • 可以作為GC Root的對象

    – 虛拟機棧(棧幀中的本地變量表)中引用的對象。

    – 方法區中類靜态屬性引用的對象

    – 方法區中常量引用的對象

    – 本地方法棧中JNI(即一般說的Native方法)引用的對象

    – 所有被同步鎖(​​

    ​synchronized​

    ​關鍵字)持有的對象。

示例

/**
 * 示範GC Roots
 */
public class Demo2_2 {

    public static void main(String[] args) throws InterruptedException, IOException {
        List<Object> list1 = new ArrayList<>();
        list1.add("a");
        list1.add("b");
        System.out.println(1);
        System.in.read();

        list1 = null;
        System.out.println(2);
        System.in.read();
        System.out.println("end...");
    }
}      

運作程式,分别生成垃圾回收前後的​

​dunp​

​檔案

jps // 檢視程序号
// format=b 生成檔案格式為二進制;live主動觸發垃圾回收,保留存活對象;file表示存放位置
jmap -dump:format=b,live,file=1.bin 程序号51125      

list1置空前,生成dunmp檔案1.bin

JVM專題(十)-垃圾回收(一)
List<Object> list1 = new ArrayList<>();      

​list1​

​​是局部變量,存在于活動棧幀;​

​new ArrayList<>()​

​​産生的對象才是存在于堆中的對象。即此處​

​new ArrayList<>()​

​對應的那個對象才能作為根對象。

list1置空後,生成dunmp檔案2.bin

JVM專題(十)-垃圾回收(一)

因為在執行

jmap -dump:format=b,live,file=2.bin 51125      

使用了live參數,主動調用了垃圾回收。由于​

​list1​

​​被置空,​

​list​

​對象無人引用,是以被垃圾回收了。是以在根對象中找不到了。

2.五種引用

JVM專題(十)-垃圾回收(一)

強引用

隻有所有 GC Roots對象都不通過【強引用】引用該對象,該對象才能被垃圾回收      

強引用對象回收

JVM專題(十)-垃圾回收(一)

軟引用

僅有【軟引用】引用該對象時,在垃圾回收後,記憶體仍不足時會再次出發垃圾回收,回收軟引用對象
可以配合【引用隊列】來釋放軟引用自身      

軟引用對象回收

JVM專題(十)-垃圾回收(一)
JVM專題(十)-垃圾回收(一)

此時A2對象可能被回收。

  • A2對象僅僅隻被軟引用對象引用
  • 在執行GC時,記憶體空間不足了,才會被垃圾回收
  • 回收後,軟引用對象本身可以通過進入引用隊列進行釋放

弱引用

僅有【弱引用】引用該對象時,在垃圾回收時,無論記憶體是否充足,都會回收弱引用對象
可以配合【引用隊列】來釋放弱引用自身      

弱引用對象的回收

JVM專題(十)-垃圾回收(一)

此時A3對象可能會被回收

  • A3對象僅僅被弱引用對象引用
  • 當執行GC時,無論記憶體是否不足,都會被垃圾回收
  • 回收後,弱引用對象本身可以通過進入引用隊列進行釋放

虛引用

必須配合【引用隊列】使用,主要配合 ByteBuffer 使用,被引用對象回收時,會将【虛引用】入隊, 由 Reference Handler 線程調用虛引用相關方法釋放【直接記憶體】
如上圖,B對象不再引用ByteBuffer對象,ByteBuffer就會被回收。但是直接記憶體中的記憶體還未被回收。這時需要将虛引用對象Cleaner放入引用隊列中,然後調用它的clean方法來釋放直接記憶體      

虛引用對象的回收

JVM專題(十)-垃圾回收(一)
JVM專題(十)-垃圾回收(一)

虛引用一般是對直接記憶體配置設定的應用。

  • 當聲明​

    ​ByteBuffer​

    ​時,​

    ​ByteBuffer​

    ​會配置設定一塊直接記憶體,并把直接記憶體的位址傳遞給虛引用對象Cleaner。
  • 當​

    ​ByteBuffer​

    ​不再被強引用時,被回收後,直接記憶體還沒有被釋放。這時會将虛引用放入虛引用的引用隊列,由​

    ​Reference Handler​

    ​線程監控,發現虛引用對象,調用虛引用相關方法​

    ​Unsafe.freeMemory​

    ​釋放直接記憶體。

終結器引用

無需手動編碼,但其内部配合【引用隊列】使用,在垃圾回收時,【終結器引用】入隊(被引用對象暫時沒有被回收),再由 Finalizer 線程通過【終結器引用】找到被引用對象并調用它的 finalize 方法,第二次 GC 時才能回收被引用對象
如上圖,B對象不再引用A4對象。這時終結器對象就會被放入引用隊列中,引用隊列會根據它,找到它所引用的對象。然後調用被引用對象的finalize方法。調用以後,該對象就可以被垃圾回收了      

終結器引用的回收

JVM專題(十)-垃圾回收(一)
  • 所有的類都繼承自Object類,裡面有一個終結方法finalize()方法,當對象重寫了finalize()方法,且沒有強引用引用它時,它就可以被當成垃圾進行垃圾回收。
  • 當對象沒有被強引用時,會由jvm為該對象建立一個對應的終結器引用。當這個對象被垃圾回收時,會将終結器引用加入引用隊列,但是對象不會被垃圾回收。
  • 再由一個優先級較低的Finalizer線程去監控引用隊列是否有終結器引用,如果有,就通過終結器引用找到A4對象,調用其finalize()方法,等調用之後,等下一次垃圾回收時,就可以被垃圾回收了。
  • 工作效率低,第一次GC不會回收對象,先将終結器引用入隊,等到第二次垃圾回收才有可能被回收。

6.代碼示例

6.1軟引用

# 虛拟機參數
-Xmx20m -XX:+PrintGCDetails -verbose:gc      
/**
 * 示範軟引用
 * -Xmx20m -XX:+PrintGCDetails -verbose:gc
 */
public class Demo2_3 {

    private static final int _4MB = 4 * 1024 * 1024;



    public static void main(String[] args) throws IOException {
        /*List<byte[]> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            list.add(new byte[_4MB]);
        }

        System.in.read();*/
        soft();


    }

    public static void soft() {
        // list --> SoftReference --> byte[]

        List<SoftReference<byte[]>> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB]);
            System.out.println(ref.get());
            list.add(ref);
            System.out.println(list.size());

        }
        System.out.println("循環結束:" + list.size());
        for (SoftReference<byte[]> ref : list) {
            System.out.println(ref.get());
        }
    }
}      
JVM專題(十)-垃圾回收(一)
/**
 * 示範軟引用, 配合引用隊列
 */
public class Demo2_4 {
    private static final int _4MB = 4 * 1024 * 1024;

    public static void main(String[] args) {
        List<SoftReference<byte[]>> list = new ArrayList<>();

        // 引用隊列
        ReferenceQueue<byte[]> queue = new ReferenceQueue<>();

        for (int i = 0; i < 5; i++) {
            // 關聯了引用隊列, 當軟引用所關聯的 byte[]被回收時,軟引用自己會加入到 queue 中去
            SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB], queue);
            System.out.println(ref.get());
            list.add(ref);
            System.out.println(list.size());
        }

        // 從隊列中擷取無用的 軟引用對象,并移除
        Reference<? extends byte[]> poll = queue.poll();
        while( poll != null) {
            list.remove(poll);
            poll = queue.poll();
        }

        System.out.println("===========================");
        for (SoftReference<byte[]> reference : list) {
            System.out.println(reference.get());
        }

    }
}      
JVM專題(十)-垃圾回收(一)

6.2 弱引用

-Xmx20m -XX:+PrintGCDetails -verbose:gc      
/**
 * 示範弱引用
 * -Xmx20m -XX:+PrintGCDetails -verbose:gc
 */
public class Demo2_5 {
    private static final int _4MB = 4 * 1024 * 1024;

    public static void main(String[] args) {
        //  list --> WeakReference --> byte[]
        List<WeakReference<byte[]>> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            WeakReference<byte[]> ref = new WeakReference<>(new byte[_4MB]);
            list.add(ref);
            for (WeakReference<byte[]> w : list) {
                System.out.print(w.get()+" ");
            }
            System.out.println();

        }
        System.out.println("循環結束:" + list.size());
    }
}      

繼續閱讀