Java 引用類型簡述

強引用 ( Strong Reference )

強引用是使用最廣泛的引用。若是一個對象具備強引用,那垃圾回收器毫不會回收它。當內存空間不足,Java虛擬機寧願拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具備強引用的對象來解決內存不足的問題。 ps:強引用其實也就是咱們平時A a = new A()這個意思。java

  • 強引用特性
    • 強引用能夠直接訪問目標對象。
    • 強引用所指向的對象在任什麼時候候都不會被系統回收。
    • 強引用可能致使內存泄漏。

<br/> ## Final Reference * 當前類是不是finalizer類,注意這裏finalizer是由JVM來標誌的( 後面簡稱f類 ),並非指java.lang.ref.Fianlizer類。可是f類是會被JVM註冊到java.lang.ref.Fianlizer類中的。緩存

① 當前類或父類中含有一個參數爲空,返回值爲void的名爲finalize的方法。
② 而且該finalize方法必須非空ide

  • GC 回收問題
    • 對象由於Finalizer的引用而變成了一個臨時的強引用,即便沒有其餘的強引用,仍是沒法當即被回收;
    • 對象至少經歷兩次GC才能被回收,由於只有在FinalizerThread執行完了f對象的finalize方法的狀況下才有可能被下次GC回收,而有可能期間已經經歷過屢次GC了,可是一直還沒執行對象的finalize方法;
    • CPU資源比較稀缺的狀況下FinalizerThread線程有可能由於優先級比較低而延遲執行對象的finalize方法;
    • 由於對象的finalize方法遲遲沒有執行,有可能會致使大部分f對象進入到old分代,此時容易引起old分代的GC,甚至Full GC,GC暫停時間明顯變長,甚至致使OOM;
    • 對象的finalize方法被調用後,這個對象其實還並無被回收,雖然可能在不久的未來會被回收。

詳見:JVM源碼分析之FinalReference徹底解讀 - 你假笨源碼分析

軟引用 ( Soft Reference )

是用來描述一些還有用但並不是必須的對象。對於軟引用關聯着的對象,在系統將要發生內存溢出異常以前,將會把這些對象列進回收範圍之中進行第二次回收。若是此次回收尚未足夠的內存,纔會拋出內存溢出異常。 對於軟引用關聯着的對象,若是內存充足,則垃圾回收器不會回收該對象,若是內存不夠了,就會回收這些對象的內存。在 JDK 1.2 以後,提供了 SoftReference 類來實現軟引用。軟引用可用來實現內存敏感的高速緩存。軟引用能夠和一個引用隊列(ReferenceQueue)聯合使用,若是軟引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。
注意:Java 垃圾回收器準備對SoftReference所指向的對象進行回收時,調用對象的 finalize() 方法以前,SoftReference對象自身會被加入到這個 ReferenceQueue 對象中,此時能夠經過 ReferenceQueue 的 poll() 方法取到它們。性能

/**
 * 軟引用:對於軟引用關聯着的對象,在系統將要發生內存溢出異常以前,將會把這些對象列進回收範圍之中進行第二次回收( 由於是在第一次回收後纔會發現內存依舊不充足,纔有了這第二次回收 )。若是此次回收尚未足夠的內存,纔會拋出內存溢出異常。
 * 對於軟引用關聯着的對象,若是內存充足,則垃圾回收器不會回收該對象,若是內存不夠了,就會回收這些對象的內存。
 * 經過debug發現,軟引用在pending狀態時,referent就已是null了。
 *
 * 啓動參數:-Xmx5m
 *
 */
public class SoftReferenceDemo {

    private static ReferenceQueue<MyObject> queue = new ReferenceQueue<>();

    public static void main(String[] args) throws InterruptedException {
        Thread.sleep(3000);
        MyObject object = new MyObject();
        SoftReference<MyObject> softRef = new SoftReference(object, queue);
        new Thread(new CheckRefQueue()).start();

        object = null;
        System.gc();
        System.out.println("After GC : Soft Get = " + softRef.get());
        System.out.println("分配大塊內存");

        /**
         * ====================== 控制檯打印 ======================
         * After GC : Soft Get = I am MyObject.
         * 分配大塊內存
         * MyObject's finalize called
         * Object for softReference is null
         * After new byte[] : Soft Get = null
         * ====================== 控制檯打印 ======================
         *
         * 總共觸發了 3 次 full gc。第一次有System.gc();觸發;第二次在在分配new byte[5*1024*740]時觸發,而後發現內存不夠,因而將softRef列入回收返回,接着進行了第三次full gc。
         */
//        byte[] b = new byte[5*1024*740];

        /**
         * ====================== 控制檯打印 ======================
         * After GC : Soft Get = I am MyObject.
         * 分配大塊內存
         * Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
         *      at com.bayern.multi_thread.part5.SoftReferenceDemo.main(SoftReferenceDemo.java:21)
         * MyObject's finalize called
         * Object for softReference is null
         * ====================== 控制檯打印 ======================
         *
         * 也是觸發了 3 次 full gc。第一次有System.gc();觸發;第二次在在分配new byte[5*1024*740]時觸發,而後發現內存不夠,因而將softRef列入回收返回,接着進行了第三次full gc。當第三次 full gc 後發現內存依舊不夠用於分配new byte[5*1024*740],則就拋出了OutOfMemoryError異常。
         */
        byte[] b = new byte[5*1024*790];

        System.out.println("After new byte[] : Soft Get = " + softRef.get());
    }

    public static class CheckRefQueue implements Runnable {

        Reference<MyObject> obj = null;

        [@Override](https://my.oschina.net/u/1162528)
        public void run() {
            try {
                obj = (Reference<MyObject>) queue.remove();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (obj != null) {
                System.out.println("Object for softReference is " + obj.get());
            }

        }
    }

    public static class MyObject {

        [@Override](https://my.oschina.net/u/1162528)
        protected void finalize() throws Throwable {
            System.out.println("MyObject's finalize called");
            super.finalize();
        }

        [@Override](https://my.oschina.net/u/1162528)
        public String toString() {
            return "I am MyObject.";
        }
    }
}

弱引用 ( Weak Reference )

用來描述非必須的對象,可是它的強度比軟引用更弱一些,被弱引用關聯的對象只能生存到下一次垃圾收集發生以前。當垃圾收集器工做時,不管當前內存是否足夠,都會回收掉只被弱引用關聯的對象。一旦一個弱引用對象被垃圾回收器回收,便會加入到一個註冊引用隊列中。
注意:Java 垃圾回收器準備對WeakReference所指向的對象進行回收時,調用對象的 finalize() 方法以前,WeakReference對象自身會被加入到這個 ReferenceQueue 對象中,此時能夠經過 ReferenceQueue 的 poll() 方法取到它們。ui

/**
 * 用來描述非必須的對象,可是它的強度比軟引用更弱一些,被弱引用關聯的對象只能生存到下一次垃圾收集發送以前。當垃圾收集器工做時,不管當前內存是否足夠,都會回收掉只被弱引用關聯的對象。一旦一個弱引用對象被垃圾回收器回收,便會加入到一個註冊引用隊列中。
 */
public class WeakReferenceDemo {

    private static ReferenceQueue<MyObject> queue = new ReferenceQueue<>();

    public static void main(String[] args) {

        MyObject object = new MyObject();
        Reference<MyObject> weakRef = new WeakReference<>(object, queue);
        System.out.println("建立的弱引用爲 : " + weakRef);
        new Thread(new CheckRefQueue()).start();

        object = null;
        System.out.println("Before GC: Weak Get = " + weakRef.get());
        System.gc();
        System.out.println("After GC: Weak Get = " + weakRef.get());

        /**
         * ====================== 控制檯打印 ======================
         * 建立的弱引用爲 : java.lang.ref.WeakReference@1d44bcfa
         * Before GC: Weak Get = I am MyObject
         * After GC: Weak Get = null
         * MyObject's finalize called
         * 刪除的弱引用爲 : java.lang.ref.WeakReference@1d44bcfa , 獲取到的弱引用的對象爲 : null
         * ====================== 控制檯打印 ======================
         */
    }

    public static class CheckRefQueue implements Runnable {

        Reference<MyObject> obj = null;

        @Override
        public void run() {
            try {
                obj = (Reference<MyObject>)queue.remove();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(obj != null) {
                System.out.println("刪除的弱引用爲 : " + obj + " , 獲取到的弱引用的對象爲 : " + obj.get());

            }

        }
    }

    public static class MyObject {

        @Override
        protected void finalize() throws Throwable {
            System.out.println("MyObject's finalize called");
            super.finalize();
        }

        @Override
        public String toString() {
            return "I am MyObject";
        }
    }
}

虛引用 ( Phantom Reference )

PhantomReference 是全部「弱引用」中最弱的引用類型。不一樣於軟引用和弱引用,虛引用沒法經過 get() 方法來取得目標對象的強引用從而使用目標對象,觀察源碼能夠發現 get() 被重寫爲永遠返回 null。
那虛引用到底有什麼做用?其實虛引用主要被用來 跟蹤對象被垃圾回收的狀態,經過查看引用隊列中是否包含對象所對應的虛引用來判斷它是否 即將被垃圾回收,從而採起行動。它並不被期待用來取得目標對象的引用,而目標對象被回收前,它的引用會被放入一個 ReferenceQueue 對象中,從而達到跟蹤對象垃圾回收的做用。
當phantomReference被放入隊列時,說明referent的finalize()方法已經調用,而且垃圾收集器準備回收它的內存了。
注意:PhantomReference 只有當 Java 垃圾回收器對其所指向的對象真正進行回收時,會將其加入到這個 ReferenceQueue 對象中,這樣就能夠追綜對象的銷燬狀況。這裏referent對象的finalize()方法已經調用過了。 因此具體用法和以前兩個有所不一樣,它必須傳入一個 ReferenceQueue 對象。當虛引用所引用對象準備被垃圾回收時,虛引用會被添加到這個隊列中。
Demo1:spa

/**
 * 虛引用也稱爲幽靈引用或者幻影引用,它是最弱的一種引用關係。一個持有虛引用的對象,和沒有引用幾乎是同樣的,隨時都有可能被垃圾回收器回收。
 * 虛引用必須和引用隊列一塊兒使用,它的做用在於跟蹤垃圾回收過程。
 * 當phantomReference被放入隊列時,說明referent的finalize()方法已經調用,而且垃圾收集器準備回收它的內存了。
 */
public class PhantomReferenceDemo {

    private static ReferenceQueue<MyObject> queue = new ReferenceQueue<>();

    public static void main(String[] args) throws InterruptedException {
        MyObject object = new MyObject();
        Reference<MyObject> phanRef = new PhantomReference<>(object, queue);
        System.out.println("建立的虛擬引用爲 : " + phanRef);
        new Thread(new CheckRefQueue()).start();

        object = null;

        int i = 1;
        while (true) {
            System.out.println("第" + i++ + "次GC");
            System.gc();
            TimeUnit.SECONDS.sleep(1);
        }

        /**
         * ====================== 控制檯打印 ======================
         * 建立的虛擬引用爲 : java.lang.ref.PhantomReference@1d44bcfa
         * 第1次GC
         * MyObject's finalize called
         * 第2次GC
         * 刪除的虛引用爲: java.lang.ref.PhantomReference@1d44bcfa , 獲取虛引用的對象 : null
         * ====================== 控制檯打印 ======================
         *
         * 再通過一次GC以後,系統找到了垃圾對象,並調用finalize()方法回收內存,但沒有當即加入PhantomReference Queue中。由於MyObject對象重寫了finalize()方法,而且該方法是一個非空實現,因此這裏MyObject也是一個Final Reference。因此地刺GC完成的是Final Reference的事情。
         * 第二次GC時,該對象真處理PhantomReference,此時,將PhantomReference加入虛引用隊列( PhantomReference Queue )。
         * 並且每次gc之間須要停頓一些時間,已給JVM足夠的處理時間;若是這裏沒有TimeUnit.SECONDS.sleep(1); 可能須要gc到第五、6次纔會成功。
         */

    }

    public static class MyObject {

        @Override
        protected void finalize() throws Throwable {
            System.out.println("MyObject's finalize called");
            super.finalize();
        }

        @Override
        public String toString() {
            return "I am MyObject";
        }
    }

    public static  class CheckRefQueue implements Runnable {

        Reference<MyObject> obj = null;

        @Override
        public void run() {
            try {
                obj = (Reference<MyObject>)queue.remove();
                System.out.println("刪除的虛引用爲: " + obj + " , 獲取虛引用的對象 : " + obj.get());
                System.exit(0);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Q:👆瞭解下System.gc()操做,若是連續調用,若前一次沒完成,後一次可能會失效,因此鏈接調用System.gc()其實做用不大?
A:關於上面例子的問題咱們要補充兩點
① 首先咱們先來看下System.gc()的doc文檔:.net

/**
     * Runs the garbage collector.
     * <p>
     * Calling the <code>gc</code> method suggests that the Java Virtual
     * Machine expend effort toward recycling unused objects in order to
     * make the memory they currently occupy available for quick reuse.
     * When control returns from the method call, the Java Virtual
     * Machine has made a best effort to reclaim space from all discarded
     * objects.
     * <p>
     * The call <code>System.gc()</code> is effectively equivalent to the
     * call:
     * <blockquote><pre>
     * Runtime.getRuntime().gc()
     * </pre></blockquote>
     *
     * @see     java.lang.Runtime#gc()
     */
    public static void gc() {
        Runtime.getRuntime().gc();
    }

當這個方法返回的時候,Java虛擬機已經盡最大努力去回收全部丟棄對象的空間了。
所以不存在這System.gc()操做連續調用時,若前一次沒完成,後一次可能會失效的狀況。以及「因此鏈接調用System.gc()其實做用不大」這個說法不對,應該說連續調用System.gc()對性能可定是有影響的,但做用之一就是能夠清除「漂浮垃圾」。
② 同時須要特別注意的是對於已經沒有地方引用的這些f對象,並不會在最近的那一次gc裏立刻回收掉,而是會延遲到下一個或者下幾個gc時才被回收,由於執行finalize方法的動做沒法在gc過程當中執行,萬一finalize方法執行很長呢,因此只能在這個gc週期裏將這個垃圾對象從新標活,直到執行完finalize方法將Final Reference從queue裏刪除,這樣下次gc的時候就真的是漂浮垃圾了會被回收。線程

Demo2:debug

public class PhantomReferenceDemo2 {

    public static void main(String[] args) {
        ReferenceQueue<MyObject> queue = new ReferenceQueue<>();
        MyObject object = new MyObject();
        Reference<MyObject> phanRef = new PhantomReference<>(object, queue);
        System.out.println("建立的虛擬引用爲 : " + phanRef);
        object = null;
        System.out.println(phanRef.get());

        System.gc();

        System.out.println("referent : " + phanRef);
        System.out.println(queue.poll() == phanRef); //true

        /**
         * ====================== 控制檯打印 ======================
         * 建立的虛擬引用爲 : java.lang.ref.PhantomReference@1d44bcfa
         * null
         * referent : java.lang.ref.PhantomReference@1d44bcfa
         * true
         * ====================== 控制檯打印 ======================
         *
         * 這裏由於MyObject沒有重寫finalize()方法,因此這裏的在System.gc()後就會處理PhantomReference加入到PhantomReference Queue中。
         */
    }

    public static class MyObject {

        @Override
        public String toString() {
            return "I am MyObject";
        }
    }
}
相關文章
相關標籤/搜索