Java幽靈引用的做用

垃圾收集過程當中,對象的可觸及狀態改變的時候,能夠把引用對象和引用隊列關聯起來【這裏說的關聯,是說垃圾收集器會把要回收的對象添加到引用隊列ReferenceQueue】,這樣在可觸及性發生變化的時候獲得「通知」。html

當垃圾收集器對加入隊列的對象改變可觸及性的時候,就能夠收到異步通知了。java

看下面的代碼:數組

1緩存

2app

3異步

4ide

5函數

6測試

7flex

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

package static_;

 

import java.lang.ref.PhantomReference;

import java.lang.ref.Reference;

import java.lang.ref.ReferenceQueue;

import java.lang.reflect.Field;

 

public class Test {

    public static boolean isRun = true;

 

    @SuppressWarnings("static-access")

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

        String abc = new String("abc");

        System.out.println(abc.getClass() + "@" + abc.hashCode());

        final ReferenceQueue<String> referenceQueue = new ReferenceQueue<String>();

        new Thread() {

            public void run() {

                while (isRun) {

                    Object obj = referenceQueue.poll();

                    if (obj != null) {

                        try {

                            Field rereferent = Reference.class

                                    .getDeclaredField("referent");

                            rereferent.setAccessible(true);

                            Object result = rereferent.get(obj);

                            System.out.println("gc will collect:"

                                    + result.getClass() + "@"

                                    + result.hashCode() + "\t"

                                    + (String) result);

                        } catch (Exception e) {

                            e.printStackTrace();

                        }

                    }

                }

            }

        }.start();

        PhantomReference<String> abcWeakRef = new PhantomReference<String>(abc,

                referenceQueue);

        abc = null;

        Thread.currentThread().sleep(3000);

        System.gc();

        Thread.currentThread().sleep(3000);

        isRun = false;

    }

}

咱們用一個線程檢測referenceQueue裏面是否是有內容,若是有內容,打印出來queue裏面的內容。

從這個例子中,咱們能夠看出來,虛引用的做用是,咱們能夠聲明虛引用來引用咱們感興趣的對象,在gc要回收的時候,gc收集器會把這個對象添加到referenceQueue,這樣咱們若是檢測到referenceQueue中有咱們感興趣的對象的時候,說明gc將要回收這個對象了。此時咱們能夠在gc回收以前作一些其餘事情,好比記錄些日誌什麼的。

———————————————-分割—————————————————-

感謝藍大牛分享下面的例子。

在java中,finalize函數原本是設計用來在對象被回收的時候來作一些操做的(相似C++的析構函數)。可是對象被GC何時回收的時間,倒是不固定的,這樣finalize函數很尷尬。虛引用能夠用來解決這個問題。

在建立虛引用的時候必須傳入一個引用隊列。在一個對象的finalize函數被調用以後,這個對象的幽靈引用會被加入到引用隊列中。經過檢查隊列的內容就知道對象是否是要準備被回收了。

幽靈引用的使用並很少見,主要是實現細粒度的內存控制。好比下面代碼實現一個緩存。程序在確認原來的對象要被回收以後,才申請內存建立新的緩存。

在上面的代碼中,每次申請新的緩存的時候,都要確保以前的字節數組被成功回收。引用隊列的remove方法會阻塞直到虛引用被加入到引用隊列中。【只有對象在內存中被移除以後纔會進入引用隊列中】【?這裏有點不太肯定。後續補發】

不過注意,這種方式可能會致使gc次數過多,程序吞吐量降低。

 

Java 中的 Reference

 在 jdk 1.2 及其之後,引入了強引用、軟引用、弱引用、虛引用這四個概念。網上不少關於這四個概念的解釋,但大可能是概念性的泛泛而談,今天我結合着代碼分析了一下,首先咱們先來看定義與大概解釋(引用類型在包 java.lang.ref 裏)。

  一、強引用(StrongReference)

    強引用不會被GC回收,而且在java.lang.ref裏也沒有實際的對應類型。舉個例子來講:
    Object obj = new Object();
    這裏的obj引用即是一個強引用,不會被GC回收。

  二、軟引用(SoftReference)

    軟引用在JVM報告內存不足的時候纔會被GC回收,不然不會回收,正是因爲這種特性軟引用在caching和pooling中用處普遍。軟引用的用法:

1

2

3

4

Object obj = new Object();

SoftReference<Object> softRef = new SoftReference(obj);

// 使用 softRef.get() 獲取軟引用所引用的對象

Object objg = softRef.get();

  三、弱引用(WeakReference)

    當GC一但發現了弱引用對象,將會釋放WeakReference所引用的對象。弱引用使用方法與軟引用相似,但回收策略不一樣。

  四、虛引用(PhantomReference)

    當GC一但發現了虛引用對象,將會將PhantomReference對象插入ReferenceQueue隊列,而此時PhantomReference所指向的對象並無被GC回收,而是要等到ReferenceQueue被你真正的處理後纔會被回收。虛引用的用法:

1

2

3

4

5

6

7

8

9

Object obj = new Object();

ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>();

PhantomReference<Object> phanRef = new PhantomReference<Object>(obj, refQueue);

// 調用phanRef.get()無論在什麼狀況下會一直返回null

Object objg = phanRef.get();

// 若是obj被置爲null,當GC發現了虛引用,GC會將phanRef插入進咱們以前建立時傳入的refQueue隊列

// 注意,此時phanRef所引用的obj對象,並無被GC回收,在咱們顯式地調用refQueue.poll返回phanRef以後

// 當GC第二次發現虛引用,而此時JVM將phanRef插入到refQueue會插入失敗,此時GC纔會對obj進行回收

Reference<? extends Object> phanRefP = refQueue.poll();

看了簡單的定義以後,咱們結合着代碼來測試一下,強引用就不用說了,軟引用的描述也很清楚,關鍵是 「弱引用」 與 「虛引用」。

弱引用

1

2

3

4

5

6

7

8

9

10

11

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

    Object obj = new Object();

    ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>();

    WeakReference<Object> weakRef = new WeakReference<Object>(obj, refQueue);

    System.out.println(weakRef.get());

    System.out.println(refQueue.poll());

    obj = null;

    System.gc();

    System.out.println(weakRef.get());

    System.out.println(refQueue.poll());

}

因爲System.gc()是告訴JVM這是一個執行GC的好時機,但具體執不執行由JVM決定,所以當JVM決定執行GC,獲得的結果即是(事實上這段代碼通常都會執行GC):

  java.lang.Object@de6ced
  null
  null
  java.lang.ref.WeakReference@1fb8ee3

從執行結果得知,經過調用weakRef.get()咱們獲得了obj對象,因爲沒有執行GC,所以refQueue.poll()返回的null,當咱們把obj = null;此時沒有引用指向堆中的obj對象了,這裏JVM執行了一次GC,咱們經過weakRef.get()發現返回了null,而refQueue.poll()返回了WeakReference對象,所以JVM在對obj進行了回收以後,纔將weakRef插入到refQueue隊列中。

虛引用

1

2

3

4

5

6

7

8

9

10

11

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

    Object obj = new Object();

    ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>();

    PhantomReference<Object> phanRef = new PhantomReference<Object>(obj, refQueue);

    System.out.println(phanRef.get());

    System.out.println(refQueue.poll());

    obj = null;

    System.gc();

    System.out.println(phanRef.get());

    System.out.println(refQueue.poll());

}

一樣,當JVM執行了GC,獲得的結果即是:

  null
  null
  null
  java.lang.ref.PhantomReference@1fb8ee3

從執行結果得知,咱們先前說的沒有錯,phanRef.get()無論在什麼狀況下,都會返回null,而當JVM執行GC發現虛引用以後,JVM並無回收obj,而是將PhantomReference對象插入到對應的虛引用隊列refQueue中,當調用refQueue.poll()返回PhantomReference對象時,poll方法會先把PhantomReference的持有隊列queue(ReferenceQueue<? super T>)置爲NULL,NULL對象繼承自ReferenceQueue,將enqueue(Reference paramReference)方法覆蓋爲return false,而此時obj再次被GC發現時,JVM再將PhantomReference插入到NULL隊列中便會插入失敗返回false,此時GC便會回收obj。事實上經過這段代碼咱們也的卻看不出來obj是否被回收,但經過 PhantomReference 的javadoc註釋中有一句是這樣寫的:

Once the garbage collector decides that an object obj is phantom-reachable, it is being enqueued on the corresponding queue, but its referent is not cleared. That is, the reference queue of the phantom reference must explicitly be processed by some application code.

翻譯一下(這句話很簡單,我相信不少人應該也看得懂):

一旦GC決定一個「obj」是虛可達的,它(指PhantomReference)將會被入隊到對應的隊列,可是它的指代並無被清除。也就是說,虛引用的引用隊列必定要明確地被一些應用程序代碼所處理。

弱引用與虛引用的用處

  軟引用很明顯能夠用來製做caching和pooling,而弱引用與虛引用呢?其實用處也很大,首先咱們來看看弱引用,舉個例子:

1

2

3

4

5

6

7

class Registry {

    private Set registeredObjects = new HashSet();

 

    public void register(Object object) {

        registeredObjects.add( object );

    }

}

全部我添加進 registeredObjects 中的object永遠不會被GC回收,由於這裏有個強引用保存在registeredObjects裏,另外一方面若是我把代碼改成以下:

1

2

3

4

5

6

7

class Registry {

     private Set registeredObjects = new HashSet();

 

     public void register(Object object) {

         registeredObjects.add( new WeakReference(object) );

     }

 }

  如今若是GC想要回收registeredObjects中的object,便可以實現了,一樣在使用HashMap若是想實現如上的效果,一種更好的實現是使用WeakHashMap。

而虛引用呢?咱們先來看看javadoc的部分說明:

Phantom references are useful for implementing cleanup operations that are necessary before an object gets garbage-collected. They are sometimes more flexible than the finalize() method.

翻譯一下:

虛引用在實現一個對象被回收以前必須作清理操做是頗有用的。有時候,他們比finalize()方法更靈活。

很明顯的,虛引用能夠用來作對象被回收以前的清理工做。

相關文章
相關標籤/搜索