1. 程式人生 > >【案例演示】JVM之強引用、軟引用、弱引用、虛引用

【案例演示】JVM之強引用、軟引用、弱引用、虛引用

1.背景

想要理解物件什麼時候回收,就要理解到物件引用這個概念,於是有了下文

2.java中引用物件結構圖

3.引用詳解

3.1.什麼是強引用

a.當記憶體不足,JVM開始垃圾回收,對於強引用的物件,就算是出現了00M也不會對該物件進行回收,死都不收。

b.強引用是我們最常見的普通物件引用,只要還有強引用指向一個物件,就能表明物件還“活著”,垃圾收集器不會碰這種物件。

在Java中最常見的就是強引用,把一個物件賦給一個引用變數,這個引用變數就是一個強引用。

當一個物件被強引用變數引用時,它處於可達狀態,它是不可能被垃圾回收機制回收的,即使該物件以後永遠都不會被用到JVM也不會回收。

因此強引用是造成Java記憶體洩漏的主要原因之一

c.對於一個普通的物件,如果沒有其他的引用關係,只要超過了引用的作用域或者顯式地將相應(強)引用賦值為null,一般認為就是可以被垃圾收集的了〈當然具體回收時機還是要看垃圾收集策略)。

案例:

package com.wfd360.demo03GC.referDemo;

/**
 * @author 姿勢帝-部落格園
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 06/20 12:12
 * @description
 */
public class StrongRefer {
    /**
     * 強引用的理解
     *
     * @param args
     */
    public static void main(String[] args) {
        Object obj1 = new Object();
        // 建立強引用
        Object obj2 = obj1;
        // 觀察obj1 和 obj2 的各種記憶體地址
        System.out.println("obj1=" + obj1);
        System.out.println("obj2=" + obj2);
        // obj1建立可以回收的條件
        obj1 = null;
        // gc回收
        System.gc();
        // 觀察各物件情況
        System.out.println("obj1=" + obj1);
        System.out.println("obj2=" + obj2);
    }
}
View Code

 從測試結果課程看出,obj1的實際物件別沒有回收;

3.2.什麼是軟引用

a.軟引用是用來描述一些還有用但並非必需的物件,需要用java.lang.ref.SoftReference類來實現。

b.對於軟引用關聯著的物件,在系統將要發生記憶體溢位異常之前,將會把這些物件列進回收範圍之中進行第二次回收。如果這次回收還沒有足夠的記憶體,才會丟擲記憶體溢位異常。在JDK1.2之後,提供了Soft Reference類來實現軟引用。

c.軟引用通常用在對記憶體敏感的程式中,比如快取記憶體就有用到軟引用,記憶體夠用的時候就保留,不夠用就回收!

案例:

package com.wfd360.demo03GC.referDemo;

import java.lang.ref.SoftReference;

/**
 * @author 姿勢帝-部落格園
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 06/20 12:12
 * @description
 */
public class SoftRefer {

    /**
     * 軟引用的理解
     * 通過設定jvm引數,在不同的條件下觀察
     *
     * @param -Xms5m -Xmx5m -XX:+PrintGCDetails
     * @param args
     */
    public static void main(String[] args) {
        // 測試記憶體充足(不回收軟引用)
        //testSoftReferNOGc();
        // 測試記憶體不充足(回收軟引用)
        testSoftReferGc();
    }

    /**
     * 模擬記憶體充足的情況
     */
    public static void testSoftReferNOGc() {
        Object obj1 = new Object();
        // 建立軟引用
        SoftReference softRefer = new SoftReference<>(obj1);
        // 觀察記憶體地址
        System.out.println("obj1=" + obj1);
        System.out.println("softRefer=" + softRefer.get());
        // obj1建立可以回收的條件
        obj1 = null;
        // gc回收
        System.gc();
        // 再次觀察記憶體地址
        System.out.println("obj1=" + obj1);
        System.out.println("softRefer=" + softRefer.get());
    }

    /**
     * 模擬記憶體不足
     * 1.設定較小的堆記憶體
     * 2.建立大物件
     * 3.jvm參
     * -Xms5m -Xmx5m -XX:+PrintGCDetails
     */
    public static void testSoftReferGc() {
        Object obj1 = new Object();
        // 建立軟引用
        SoftReference softRefer = new SoftReference<>(obj1);
        // 觀察記憶體地址
        System.out.println("obj1=" + obj1);
        System.out.println("softRefer=" + softRefer.get());
        // obj1建立可以回收的條件
        obj1 = null;
        try {
            byte[] bytes = new byte[6 * 1024 * 1024];
        } catch (Throwable e) {
            System.out.println("===============>error:" + e.getMessage());
        } finally {
            // 再次觀察記憶體地址
            System.out.println("obj1=" + obj1);
            System.out.println("softRefer=" + softRefer.get());
        }
    }
}
View Code

記憶體充足測試結果:

 記憶體不充足測試結果:

 實際案例

假如有一個應用需要讀取大量的本地資料(圖片、通訊率、臨時檔案等):

如果每次讀取資料都從硬碟讀取則會嚴重影響效能,

如果一次性全部載入到記憶體中又可能造成記憶體溢位。

此時使用軟引用可以解決這個問題。

設計思路是:用一個HashMap來儲存資料的路徑和相應資料物件關聯的軟引用之間的對映關係,在記憶體不足時,

JVM會自動回收這些快取資料物件所佔用的空間,從而有效地避免了00M的問題。

Map<String,SoftReference>imageCache=new HashMap<String,SoftReference>();

 3.3.什麼是弱引用

a.弱引用也是用來描述非必需物件的,但是它的強度比軟引用更弱一些,被弱引用關聯的物件只能生存到下一次垃圾收集發生之前。

b..當垃圾收集器工作時,無論當前記憶體是否足夠,都會回收掉只被弱引用關聯的物件。在JDK1.2之後,提供廣Weak Reference類來實現弱引用。

c.弱引用需要用Java.lang.ref.WeakReference類來實現,它比軟引用的生存期更短.

案例:

package com.wfd360.demo03GC.referDemo;

import java.lang.ref.WeakReference;

/**
 * @author 姿勢帝-部落格園
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 06/20 12:12
 * @description
 */
public class WeakRefer {

    /**
     * 弱引用的理解
     *
     * @param args
     */
    public static void main(String[] args) {
        Object obj1 = new Object();
        // 建立弱引用
        WeakReference softRefer = new WeakReference<>(obj1);
        // 觀察記憶體地址
        System.out.println("obj1=" + obj1);
        System.out.println("softRefer=" + softRefer.get());
        // obj1建立可以回收的條件
        obj1 = null;
        // gc回收
        System.gc();
        // 再次觀察記憶體地址
        System.out.println("obj1=" + obj1);
        System.out.println("softRefer=" + softRefer.get());
    }

}
View Code

 擴充套件知識-WeakHashMap

檢視API介紹:

 測試程式碼:

package com.wfd360.demo03GC.referDemo;

import java.util.HashMap;
import java.util.WeakHashMap;

/**
 * @author 姿勢帝-部落格園
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 06/20 5:10
 * @description <p>
 * 弱引用引用之:WeakHashMap
 * 以弱鍵 實現的基於雜湊表的 Map。在 WeakHashMap 中,當某個鍵不再正常使用時,將自動移除其條目。
 * 更精確地說,對於一個給定的鍵,其對映的存在並不阻止垃圾回收器對該鍵的丟棄,這就使該鍵成為可終止的,被終止,
 * 然後被回收。丟棄某個鍵時,其條目從對映中有效地移除,因此,該類的行為與其他的 Map 實現有所不同。
 * </p>
 */
public class WeakReferMap {
    /**
     * 測試 HashMap 與 WeakHashMap 區別
     * 測試邏輯:
     * 1.建立不同的map
     * 2.建立key  value值
     * 3.放入各自的map,並列印結果
     * 4.將key設定為null,並列印結果
     * 5.手動GC,並列印結果
     *
     * @param args
     */
    public static void main(String[] args) {
        hashMapMethod();
        System.out.println("--------華麗的分割線--------");
        weakHashMapMethod();
    }

    /**
     * HashMap測試(強引用)
     */
    private static void hashMapMethod() {
        HashMap<String, String> map = new HashMap<>();
        String key = "key1";
        String value = "HashMap-value";

        map.put(key, value);
        System.out.println(map);

        key = null;
        System.out.println(map);

        System.gc();
        System.out.println(map);
    }

    /**
     * 若引用(WeakHashMap測試)
     */
    private static void weakHashMapMethod() {
        WeakHashMap<String, String> map = new WeakHashMap<>();
        // 注意這裡的new一個字串與直接寫key="key2"對測試結果是有區別的,詳細原因可以看之前講的記憶體分配
        String key = new String("key2");
        String value = "WeakHashMap-value";

        map.put(key, value);
        System.out.println(map);

        key = null;
        System.out.println(map);

        System.gc();
        System.out.println(map);

    }

}
View Code

測試結果:

 從測試結果可以看出:弱引用的map資料已經被回收。

 擴充套件知識-ReferenceQueue引用佇列

 程式碼:

package com.wfd360.demo03GC.referDemo;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;

/**
 * @author 姿勢帝-部落格園
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 06/20 7:23
 * @description
 */
public class QueueRefer {
    /**
     * 測試弱引用回收前,把資料放入佇列中
     *
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        Object obj1 = new Object();
        ReferenceQueue<Object> referenceQueue = new ReferenceQueue();
        // 當GC釋放物件記憶體的時候,會將引用加入到引用佇列
        WeakReference<Object> weakReference = new WeakReference<>(obj1, referenceQueue);

        System.out.println(obj1);
        System.out.println(weakReference.get());
        System.out.println(referenceQueue.poll());

        System.out.println("--------華麗的分割線--------");
        obj1 = null;
        System.gc();
        Thread.sleep(500);

        System.out.println(obj1);
        System.out.println(weakReference.get());
        System.out.println(referenceQueue.poll());
    }

}
View Code

採用弱引用的方式測試結果:

從測試結果可以看出,需要回收的物件已經進入佇列。

 採用軟引用的方式測試結果:

 從測試結果可以看出,軟引用,沒有到達回收的條件,並沒有進行回收,也不會進入佇列;

3.4.什麼是虛引用

1.虛引用需要java.lang.ref.PhantomReference類來實現。

2.與其他幾種引用都不同,虛引用並不會決定物件的生命週期。如果一個物件僅持有

虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收,它不能單獨使用也不能通過它訪

問物件,虛引用必須和引用佇列(ReferenceQueue)聯合使用。

3.虛引用的主要作用是跟蹤物件被垃圾回收的狀態。僅僅是提供了一種確保物件被finalize以後,做某些事情的

機制。PhantomReference的get方法總是返回null,因此無法訪問對應的引用物件。其意義在於說明一個物件己

經進入俑finalization階段,可以被gc回收,用來實現比finalization機制更靈活的回收操作。

4.設定虛引用關聯的唯一目的,就是在這個物件被收集器回收的時候收到一個系統通知或者後續新增

進一步的處理。Java技術允許使用finalize()方法在垃圾收集器將物件從記憶體中清除出去之前做必要的清理工作。

程式碼:

package com.wfd360.demo03GC.referDemo;

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;

/**
 * @author 姿勢帝-部落格園
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 06/20 7:44
 * @description
 */
public class PhantomRefer {
    /**
     * 虛引用測試
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        Object obj1 = new Object();
        ReferenceQueue<Object> referenceQueue = new ReferenceQueue();
        PhantomReference<Object> phantomReference = new PhantomReference<>(obj1,referenceQueue);

        System.out.println(obj1);
        System.out.println(phantomReference.get());
        System.out.println(referenceQueue.poll());

        System.out.println("--------華麗的分割線--------");

        obj1 = null;
        System.gc();
        Thread.sleep(500);

        System.out.println(obj1);
        System.out.println(phantomReference.get());
        System.out.println(referenceQueue.poll());
    }

}
View Code

測試結果:

4.重要總結

物件是否存活判斷流程:

1.可達性分析,看是否有GC Roots的引用鏈,如果沒有將做第一次標記;

2.檢查是否需要執行finalize()方法,

如果沒必要(之前執行過了),直接回收記憶體;

如果要執行finalize()方法,這個時候物件如果再次建立引用鏈(唯一自救機會),物件不會被回收,否則直接回收;

總結:

1.物件回收滿足兩個條件:

a.沒有引用鏈。

b.回收前會執行finalize()方法,如果執行finalize(),沒有再次建立連線(如果重新與引用鏈上的任意物件建立連線,例如給物件賦值,該物件都不會被回收)

2.在gc回收前會執行finalize()方法,只執行一次,並且是非同步執行不保證執行成功,執行緒優先順序低

程式碼演示:

package com.wfd360.demo03GC.referDemo;

/**
 * @author 姿勢帝-部落格園
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 06/20 8:34
 * @description
 */
public class FinalizeGC {
    public static FinalizeGC obj1 = null;

    /**
     * 重寫finalize方法
     * @throws Throwable
     */
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("執行finalize方法");
        // 自救,在回收時建立引用鏈
        FinalizeGC.obj1 = this;
    }

    public static void main(String[] args) throws InterruptedException {
        obj1  = new FinalizeGC();

        obj1 = null;
        System.gc();
        Thread.sleep(600);
        System.out.println("第一次自救成功:"+obj1);

        obj1 = null;
        System.gc();
        Thread.sleep(600);
        System.out.println("第二次自救失敗,不會再次執行finalize方法:"+obj1);
    }
}
View Code

測試結果:

 完美!