Java中的四種參考型別 Strong, Soft, Weak And Phantom__Java

來源:互聯網
上載者:User

前言

總所周知, 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;                  //將這個引用指向null 指標,                                        //那麼上面那個剛new來的對象就沒用任何其它有效引用指向它了                                        //也就說該對象對於垃圾收集器是合格                                        //因此在接下來某個時間點 GC進行收集動作的時候, 該對象將會被銷毀,記憶體被釋放    }}class Sample {}

也可以畫個簡單的圖理解一下:
2. 軟引用(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);        //將強引用指向null 指標 那麼此時只有一個軟引用指向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);        //將強引用指向null 指標 那麼此時只有一個軟引用指向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];    }}

運行 輸出結果:

example.Sample@3f580216example.Sample@3f580216example.Sample@3f580216example.Sample@3f580216example.Sample@3f580216example.Sample@3f580216example.Sample@3f580216nullnullException 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);        //將強引用指向null 指標 那麼此時只有一個軟引用指向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];    }}

運行 輸出結果:

example.Sample@2a3c142cexample.Sample@2a3c142cexample.Sample@2a3c142cexample.Sample@2a3c142cexample.Sample@2a3c142cexample.Sample@2a3c142cexample.Sample@2a3c142c--- 軟引用對象被jvm回收了 ---- java.lang.ref.SoftReference@306d3b64null--- 回收對象 ---- nullnullException in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space    at example.Main$1.run(Main.java:47)
3. 弱引用(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);        //將強引用指向null 指標 那麼此時只有一個弱引用指向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];    }}

運行 輸出結果:

example.Sample@234c0f50example.Sample@234c0f50--- 弱引用對象被jvm回收了 ---- java.lang.ref.WeakReference@16fea746--- 回收對象 ---- nullnullnullnullnullnullnullnullException 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     * <code>null</code>.     *     * @return  <code>null</code>     */    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];    }}

運行 結果:

nullnull--- 虛幻引用對象被jvm回收了 ---- java.lang.ref.PhantomReference@40788638false--- 回收對象 ---- nullnullnullnullnull

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.