1. 程式人生 > >Java物件強引用,軟引用,弱引用,虛引用

Java物件強引用,軟引用,弱引用,虛引用

前言

總所周知, java不同於c/c++,它不需要程式設計師自已來管理記憶體(分配,釋放記憶體),java 會自己來管理記憶體,比如銷燬某些不再被使用的物件。這些操作都是在一個後臺執行緒默默進行(Garbage Collector Thread),也就是垃圾收集器執行緒,根據jvm實現的策略來釋放物件記憶體。但是程式編寫者卻無法控制這個後臺執行緒,無法讓它在你想要的時候開始釋放記憶體,銷燬物件,按照你的規定來銷燬那些物件,釋放記憶體,這些都是都jvm自己控制的。但是隨著 java.lang.ref這個包下的類的引進,程式設計師擁有了一點點控制你建立的物件何時釋放,銷燬的權利,當然只是一點點。那接下來就來看看這些類和java中的四種引用型別有何對應關係。先來看看這四種引用型別,因為jvm在進行垃圾收集的時候,需要判斷哪些物件需要銷燬,這時就與這幾種引用型別相關。

以下是這幾種引用型別:

  1. 強引用(Strong References)
    強引用型別是我們平時寫程式碼的時候最常用的引用,而大部分人往往都會忽略這個概念,都成一種理所當然的事情了。
    接下來看看下面這個簡單的例子:
public class Main {


    public static void main(String[] args) {

        //建立一個物件,new出來的物件都是分配在java堆中的
        Sample sample = new Sample();   //sample這個引用就是強引用

        sample = null;                  //將這個引用指向空指標,
                                        //那麼上面那個剛new來的物件就沒用任何其它有效的引用指向它了
                                        //也就說該物件對於垃圾收集器是符合條件的
                                        //因此在接下來某個時間點 GC進行收集動作的時候, 該物件將會被銷燬,記憶體被釋放

    }

}

class Sample {

}

也可以畫個簡單的圖理解一下:
在這裡插入圖片描述

  1. 軟引用(Soft References)
    軟引用在java.lang.ref包中有與之對應的類java.lang.ref.SoftReference。
    重點: 被弱引用指向的物件不會被垃圾收集器收集(即使該物件沒有強引用指向它),除非jvm使用記憶體不夠了,才會對這類物件進行銷燬,釋放記憶體。舉個簡單的例子:
public class Main {

    public static void main(String[] args) {

        //建立一個物件,new出來的物件都是分配在java堆中的
        Sample sample = new Sample();   //sample這個引用就是強引用

        //建立一個軟引用指向這個物件   那麼此時就有兩個引用指向Sample物件
        SoftReference<Sample> softRef = new SoftReference<Sample>(sample);

        //將強引用指向空指標 那麼此時只有一個軟引用指向Sample物件
        //注意:softRef這個引用也是強引用,它是指向SoftReference這個物件的
        //那麼這個軟引用在哪呢? 可以跟一下java.lang.Reference的原始碼 
        //private T referent; 這個才是軟引用, 只被jvm使用
        sample = null;

        //可以重新獲得Sample物件,並用一個強引用指向它
        sample = softRef.get();
    }

}

class Sample {

}

有興趣可以去看看Reference的原始碼。
在這裡插入圖片描述

現在是不是想到了軟引用的一個使用場景,它相比與強引用可以避免OOM。

現在可以簡單的測試下 當jvm記憶體不足的情況下,軟引用的回收情況。
為了更快的看到結果,我限制了jvm的最大堆記憶體 -Xmx100m 為100m

public class Main {

    private static final List<Object> TEST_DATA = new LinkedList<>();


    public static void main(String[] args) throws InterruptedException {

        //建立一個物件,new出來的物件都是分配在java堆中的
        Sample sample = new Sample();   //sample這個引用就是強引用

        //建立一個軟引用指向這個物件   那麼此時就有兩個引用指向Sample物件
        SoftReference<Sample> softRef = new SoftReference<Sample>(sample);

        //將強引用指向空指標 那麼此時只有一個軟引用指向Sample物件
        //注意:softRef這個引用也是強引用,它是指向SoftReference這個物件的
        //那麼這個軟引用在哪呢? 可以跟一下java.lang.Reference的原始碼
        //private T referent; 這個才是軟引用, 只被jvm使用
        sample = null;

        //可以重新獲得Sample物件,並用一個強引用指向它
        //sample = softRef.get();

        new Thread(){
            @Override
            public void run() {
                while (true) {
                    System.out.println(softRef.get());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    TEST_DATA.add(new byte[1024 * 1024 * 5]);
                }
            }
        }.start();

        Thread.currentThread().join();
    }

}

class Sample {
    private final byte[] data;

    public Sample() {
        data = new byte[1024 * 1024 * 10];
    }
}

執行 輸出結果:

[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
null
null
Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space
	at example.Main$1.run(Main.java:42)

可以看到當jvm記憶體耗盡的時候,會將弱引用的物件進行回收 上面的例子的10m,且剛好還可以分配兩次 5m一次 ,輸出了兩次null也證明了這一點

當然我們在建立軟引用時,還可以傳入ReferenceQueue,這個佇列有啥用呢? 當jvm回收某個軟引用物件之後會將該SoftReference物件(例子中的softRef物件)新增進這個佇列,因此我們就知道這個物件啥時候被回收了,可以做一些我們想做的操作。

public SoftReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
        this.timestamp = clock;
}

Reference(T referent, ReferenceQueue<? super T> queue) {
        this.referent = referent;
        this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}

還是舉個簡單的例子

public class Main {

    private static final List<Object> TEST_DATA = new LinkedList<>();

    private static final ReferenceQueue<Sample> QUEUE = new ReferenceQueue<>();


    public static void main(String[] args) {

        //建立一個物件,new出來的物件都是分配在java堆中的
        Sample sample = new Sample();   //sample這個引用就是強引用

        //建立一個軟引用指向這個物件   那麼此時就有兩個引用指向Sample物件
        SoftReference<Sample> softRef = new SoftReference<Sample>(sample, QUEUE);

        //將強引用指向空指標 那麼此時只有一個軟引用指向Sample物件
        //注意:softRef這個引用也是強引用,它是指向SoftReference這個物件的
        //那麼這個軟引用在哪呢? 可以跟一下java.lang.Reference的原始碼
        //private T referent; 這個才是軟引用, 只被jvm使用
        sample = null;

        //可以重新獲得Sample物件,並用一個強引用指向它
        //sample = softRef.get();

        new Thread(){
            @Override
            public void run() {
                while (true) {
                    System.out.println(softRef.get());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        Thread.currentThread().interrupt();
                    }
                    TEST_DATA.add(new byte[1024 * 1024 * 5]);
                }
            }
        }.start();

        new Thread(){
            @Override
            public void run() {
                while (true) {
                    Reference<? extends Sample> poll = QUEUE.poll();
                    if (poll != null) {
                        System.out.println("--- 軟引用物件被jvm回收了 ---- " + poll);
                        System.out.println("--- 回收物件 ---- " + poll.get());
                    }
                }
            }
        }.start();

        try {
            Thread.currentThread().join();
        } catch (InterruptedException e) {
            System.exit(1);
        }
    }

}

class Sample {
    private final byte[] data;

    public Sample() {
        data = new byte[1024 * 1024 * 10];
    }
}

執行 輸出結果:

[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
exa[email protected]
--- 軟引用物件被jvm回收了 ---- [email protected]
null
--- 回收物件 ---- null
null
Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space
	at example.Main$1.run(Main.java:47)
  1. 弱引用(Weak References)
    弱引用會被jvm忽略,也就說在GC進行垃圾收集的時候,如果一個物件只有弱引用指向它,那麼和沒有引用指向它是一樣的效果,jvm都會對它就行果斷的銷燬,釋放記憶體。其實這個特性是很有用的,jdk也提供了java.util.WeakHashMap這麼一個key為弱引用的Map。比如某個資源物件你要釋放(比如 db connection), 但是如果被其它map作為key強引用了,就無法釋放,被jvm收集。

來個簡單的例子:


public class Main {

    private static final List<Object> TEST_DATA = new LinkedList<>();

    private static final ReferenceQueue<Sample> QUEUE = new ReferenceQueue<>();


    public static void main(String[] args) {

        //建立一個物件,new出來的物件都是分配在java堆中的
        Sample sample = new Sample();   //sample這個引用就是強引用

        //建立一個弱引用指向這個物件   那麼此時就有兩個引用指向Sample物件
        //SoftReference<Sample> softRef = new SoftReference<Sample>(sample, QUEUE);
        WeakReference<Sample> weakRef = new WeakReference<Sample>(sample, QUEUE);
        //將強引用指向空指標 那麼此時只有一個弱引用指向Sample物件
        //注意:softRef這個引用也是強引用,它是指向SoftReference這個物件的
        //那麼這個弱引用在哪呢? 可以跟一下java.lang.Reference的原始碼
        //private T referent; 這個才是弱引用, 只被jvm使用
        sample = null;

        //可以重新獲得Sample物件,並用一個強引用指向它
        //sample = softRef.get();

        new Thread(){
            @Override
            public void run() {
                while (true) {
                    System.out.println(weakRef.get());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        Thread.currentThread().interrupt();
                    }
                    TEST_DATA.add(new byte[1024 * 1024 * 5]);
                }
            }
        }.start();

        new Thread(){
            @Override
            public void run() {
                while (true) {
                    Reference<? extends Sample> poll = QUEUE.poll();
                    if (poll != null) {
                        System.out.println("--- 弱引用物件被jvm回收了 ---- " + poll);
                        System.out.println("--- 回收物件 ---- " + poll.get());
                    }
                }
            }
        }.start();

        try {
            Thread.currentThread().join();
        } catch (InterruptedException e) {
            System.exit(1);
        }
    }

}

class Sample {
    private final byte[] data;

    public Sample() {
        data = new byte[1024 * 1024 * 10];
    }
}

執行 輸出結果:

[email protected]
[email protected]
--- 弱引用物件被jvm回收了 ---- [email protected]
--- 回收物件 ---- null
null
null
null
null
null
null
null
Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space
	at example.Main$1.run(Main.java:47)
	```
從結果中可以清晰的看到弱引用物件並不需要在jvm耗盡記憶體的情況下才進行回收, 是可以隨時回收的。

4. 虛幻引用(Phantom References)
虛幻應用和弱引用的回收機制差不多,都是可以被隨時回收的。但是不同的地方是,它的構造方法必須強制傳入ReferenceQueue,因為在jvm回收前(重點: 對,就是回收前,軟引用和弱引用都是回收後),會將PhantomReference物件加入ReferenceQueue中; 還有一點就是PhantomReference.get()方法永遠返回空,不管物件有沒有被回收。

原始碼: java.lang.ref.PhantomReference
/**
* Returns this reference object’s referent. Because the referent of a
* phantom reference is always inaccessible, this method always returns
* null.
*
* @return null
*/
public T get() {
return null;
}


來個例子看看:

public class Main {

private static final List<Object> TEST_DATA = new LinkedList<>();

private static final ReferenceQueue<Sample> QUEUE = new ReferenceQueue<>();


public static void main(String[] args) {

    Sample sample = new Sample();

    PhantomReference<Sample> phantomRef = new PhantomReference<>(sample, QUEUE);
    sample = null;

    new Thread(){
        @Override
        public void run() {
            while (true) {
                System.out.println(phantomRef.get());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    Thread.currentThread().interrupt();
                }
                TEST_DATA.add(new byte[1024 * 1024 * 5]);
            }
        }
    }.start();

    new Thread(){
        @Override
        public void run() {
            while (true) {
                Reference<? extends Sample> poll = QUEUE.poll();
                if (poll != null) {
                    System.out.println("--- 虛幻引用物件被jvm回收了 ---- " + poll);
                    System.out.println(poll.isEnqueued());
                    System.out.println("--- 回收物件 ---- " + poll.get());
                }
            }
        }
    }.start();

    try {
        Thread.currentThread().join();
    } catch (InterruptedException e) {
        System.exit(1);
    }
}

}

class Sample {
private final byte[] data;

public Sample() {
    data = new byte[1024 * 1024 * 10];
}

}

執行 結果:

null
null
— 虛幻引用物件被jvm回收了 ---- [email protected]
false
— 回收物件 ---- null
null
null
null
null


原文:https://blog.csdn.net/rodbate/article/details/72857447