android 中的引用

轉自:http://www.jiangwenrou.com/java%E7%9A%84%E5%9B%9B%E7%A7%8D%E5%BC%95%E7%94%A8.html

參考鏈接 http://www.infoq.com/cn/articles/cf-java-garbage-references


總體思路爲:由於handle要在不同avtivity之間傳遞消息,隱含着其有一個activity的引用存放在message queen中,所以當一個activity要切換的時候(比如用戶按下退出鍵),android的垃圾回收機制本來是想回收這個activity的,但是由於handle對應的message queen中還有引用,所以無法回收,內存泄漏。如果對於handle引用的這些activity都用弱引用來指向,那麼一旦activity自己的強引用消失了,那麼這些弱引用也自動會消失,這塊內存會被釋放掉。而之所以使用ReferenceQueue的原因是爲了每次方便判斷這個弱引用還在不在,是不是已經被系統回收掉了。

準確的說,應該是在一個線程中要使用handler的時候需要這樣做,這裏說的線程,對於activity的話就是一個UI線程,一個繼承了activity的類中要使用一個handler對象,或者一個繼承了Thread的類中要使用一個handler對象,這兩個類都需要建一個弱引用的ReferenceQueue。之後調用ReferenceQueue.get()函數測試一下當前引用是否已經被回收


Java中存在四種引用,它們分別是:強引用(StrongReference),軟引用(SoftReference),弱引用(WeakReference),虛引用(PhantomReference).下面分別介紹:
強引用(StrongReference)
強引用是使用最普遍的引用。如果一個對象具有強引用,那垃圾回收器絕不會回收它。當內存空間不足,Java虛擬機寧願拋出OutOfMemoryError錯誤, 使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內存不足的問題。

實際編碼中最常見的一種引用類型。常見形式如:A a = new A();等。強引用本身存儲在棧內存中,其存儲指向對內存中對象的地址。一般情況下, 當對內存中的對象不再有任何強引用指向它時,垃圾回收機器開始考慮可能要對此內存進行的垃圾回收。如當進行編碼:a = null, 此時,剛剛在堆中分配地址並新建的a對象沒有其他的任何引用,當系統進行垃圾回收時,堆內存將被垃圾回收。

SoftReference、WeakReference、PhantomReference都是類java.lang.ref.Reference的子類。Reference作爲抽象基類, 定義了其子類對象的基本操作。Reference子類都具有如下特點:1.Reference子類不能無參化直接創建,必須至少以強引用對象爲構造參數,創建各自的子類對象;2.因爲1中以強引用對象爲構造參數創建對象,因此,使得原本強引用所指向的堆內存中的對象將不再只與強引用本身直接關聯, 與Reference的子類對象的引用也有一定聯繫。且此種聯繫將可能影響到對象的垃圾回收。

根據不同的子類對象對其指示對象(強引用所指向的堆內存中的對象)的垃圾回收不同的影響特點,分別形成了三個子類,即SoftReference、WeakReference和PhantomReference。

軟引用(SoftReference)
如果一個對象只具有軟引用,則內存空間足夠,垃圾回收器就不會回收它;如果內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它, 該對象就可以被程序使用。軟引用可用來實現內存敏感的高速緩存。

軟引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。

軟引用的一般使用形式如下:

A a = new A();
SoftReference<A> srA = new SoftReference<A>(a);

通過對象的強引用爲參數,創建了一個SoftReference對象,並使棧內存中的wrA指向此對象。此時,進行如下編碼:a = null, 對於原本a所指向的A對象的垃圾回收有什麼影響呢?先直接看一下下面一段程序的輸出結果:

import java.lang.ref.SoftReference;

public class ReferenceTest {
     public static void main(String[] args) {
        A a = new A();
         SoftReference<A> srA = new SoftReference<A>(a);
        a = null;
         if (srA.get() == null) {
            System.out.println("a對象進入垃圾回收流程");
         } else {
             System.out.println("a對象尚未被回收" + srA.get());
         }
         // 垃圾回收
        System.gc();
        if (srA.get() == null) {
             System.out.println("a對象進入垃圾回收流程");
         } else {
            System.out.println("a對象尚未被回收" + srA.get());
         }
     }
 }

 class A {

 }

輸出結果爲:

a對象尚未被回收A@4807ccf6
a對象尚未被回收A@4807ccf6

當 a = null後,堆內存中的A對象將不再有任何的強引用指向它,但此時尚存在srA引用的對象指向A對象。當第一次調用srA.get()方法返回此指示對象時, 由於垃圾回收器很有可能尚未進行垃圾回收,此時get()是有結果的,這個很好理解。當程序執行System.gc();強制垃圾回收後, 通過srA.get(),發現依然可以得到所指示的A對象,說明A對象並未被垃圾回收。那麼,軟引用所指示的對象什麼時候纔開始被垃圾回收呢?需要滿足如下兩個條件:

1.當其指示的對象沒有任何強引用對象指向它;2.當虛擬機內存不足時。 因此,SoftReference變相的延長了其指示對象佔據堆內存的時間,直到虛擬機內存不足時垃圾回收器纔回收此堆內存空間.

弱引用(WeakReference)
弱引用與軟引用的區別在於:只具有弱引用的對象擁有更短暫的生命週期。在垃圾回收器線程掃描它所管轄的內存區域的過程中,一旦發現了只具有弱引用的對象, 不管當前內存空間足夠與否,都會回收它的內存。不過,由於垃圾回收器是一個優先級很低的線程,因此不一定會很快發現那些只具有弱引用的對象。

弱引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果弱引用所引用的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關聯的引用隊列中。

同樣的,軟引用的一般使用形式如下:

A a = new A();
WeakReference<A> wrA = new WeakReference<A>(a);

當沒有任何強引用指向此對象時, 其垃圾回收又具有什麼特性呢?

import java.lang.ref.WeakReference;

public class ReferenceTest {

    public static void main(String[] args) {

        A a = new A();
        WeakReference<A> wrA = new WeakReference<A>(a);
        a = null;
        if (wrA.get() == null) {
            System.out.println("a對象進入垃圾回收流程");
        } else {
            System.out.println("a對象尚未被回收" + wrA.get());
        }
        // 垃圾回收
        System.gc();
        if (wrA.get() == null) {
            System.out.println("a對象進入垃圾回收流程");
        } else {
            System.out.println("a對象尚未被回收" + wrA.get());
        }
    }

}

class A {

}

輸出結果爲:

a對象尚未被回收A@52e5376a
a對象進入垃圾回收流程

輸出的第一條結果解釋同上。當進行垃圾回收後,wrA.get()將返回null,表明其指示對象進入到了垃圾回收過程中。因此,對弱引用特點總結爲:

WeakReference不改變原有強引用對象的垃圾回收時機,一旦其指示對象沒有任何強引用對象時,此對象即進入正常的垃圾回收流程。

那麼,依據此特點,很可能有疑問:WeakReference存在又有什麼意義呢?

其主要使用場景見於:當前已有強引用指向強引用對象,此時由於業務需要,需要增加對此對象的引用,同時又不希望改變此引用的垃圾回收時機, 此時WeakReference正好符合需求,常見於一些與生命週期的場景中。

下面給出一個Android中關於WeakReference使用的場景——結合靜態內部類和WeakReference來解決Activity中可能存在的Handler內存泄露問題。

Activity中我們需要新建一個線程獲取數據,使用handler - sendMessage方式。下面是這一過程的一般性代碼:

public class MainActivity extends Activity {

    //...
    private int page;
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == 1) {
                //...
                page++;
            } else {
                //...
            }
        };
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //...
        new Thread(new Runnable() {
            @Override
            public void run() {
                //..
                Message msg = Message.obtain();
                msg.what = 1;
                //msg.obj = xx;
                handler.sendMessage(msg);
            }
        }).start();
        //...
    }
}

IDE將會看到警示信息:This Handler class should be static or leaks might occur ..點擊查看此信息,其詳情中對問題進行了說明並給出了建議性的解決方案。

Issue: Ensures that Handler classes do not hold on to a reference to an outer class
Id: HandlerLeak

Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected.
If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue.
 If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration,
 as follows: Declare the Handler as a static class;In the outer class, instantiate a WeakReference to the outer
 class and pass this object to your Handler when you instantiate the Handler; Make all references to members of
 the outer class using the WeakReference object.

大致的意思是建議將Handler定義成內部靜態類,並在此靜態內部類中定義一個WeakReference的引用,由於指示外部的Activity對象。

問題分析:

Activity具有自身的生命週期,Activity中新開啓的線程運行過程中,可能此時用戶按下了Back鍵,或系統內存不足等希望回收此Activity, 由於Activity中新起的線程並不會遵循Activity本身的什麼週期,也就是說,當Activity執行了onDestroy,由於線程以及Handler 的HandleMessage的存在, 使得系統本希望進行此Activity內存回收不能實現,因爲非靜態內部類中隱性的持有對外部類的引用,導致可能存在的內存泄露問題。

因此,在Activity中使用Handler時,一方面需要將其定義爲靜態內部類形式,這樣可以使其與外部類(Activity)解耦,不再持有外部類的引用, 同時由於Handler中的handlerMessage一般都會多少需要訪問或修改Activity的屬性,此時,需要在Handler內部定義指向此Activity的WeakReference, 使其不會影響到Activity的內存回收同時,可以在正常情況下訪問到Activity的屬性。

Google官方給出的建議寫法爲:

public class MainActivity extends Activity {

    //...
    private int page;
    private MyHandler mMyHandler = new MyHandler(this);
    private static class MyHandler extends Handler {
        private WeakReference<MainActivity> wrActivity;
        public MyHandler(MainActivity activity) {
            this.wrActivity = new WeakReference<MainActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            if (wrActivity.get() == null) {
                return;
            }
            MainActivity mActivity = wrActivity.get();
            if (msg.what == 1) {
                //...
                mActivity.page++;
            } else {
                //...
            }
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //...
        new Thread(new Runnable() {
            @Override
            public void run() {
                //..
                Message msg = Message.obtain();
                msg.what = 1;
                //msg.obj = xx;
                mMyHandler.sendMessage(msg);
            }
        }).start();
        //...
    }
}

對於SoftReference和WeakReference,還有一個構造器參數爲ReferenceQueue,當SoftReference或WeakReference所指示的對象確實被垃圾回收後, 其引用將被放置於ReferenceQueue中。注意上文中,當SoftReference或WeakReference的get()方法返回null時,僅是表明其指示的對象已經進入垃圾回收流程, 此時對象不一定已經被垃圾回收。而只有確認被垃圾回收後,如果ReferenceQueue,其引用纔會被放置於ReferenceQueue中。

看下面的一個例子:

public class ReferenceTest {

    public static void main(String[] args) {
        A a = new A();
        WeakReference<A> wrA = new WeakReference<A>(a);
        a = null;
        if (wrA.get() == null) {
            System.out.println("a對象進入垃圾回收流程");
        } else {
            System.out.println("a對象尚未被回收" + wrA.get());
        }
        // 垃圾回收
        System.gc();
        if (wrA.get() == null) {
            System.out.println("a對象進入垃圾回收流程");
        } else {
            System.out.println("a對象尚未被回收" + wrA.get());
        }
    }
}

class A {
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("in A finalize");
    }
}

輸出結果爲:

對象尚未被回收A@46993aaa
a對象被回收
in A finalize

由此,也驗證了上文中的“進入垃圾回收流程”的說法。下面結合ReferenceQueue,看一段代碼:

public class ReferenceTest {
    public static void main(String[] args) {
        A a = new A();
        ReferenceQueue<A> rq = new ReferenceQueue<A>();
        WeakReference<A> wrA = new WeakReference<A>(a, rq);
        a = null;
        if (wrA.get() == null) {
            System.out.println("a對象進入垃圾回收流程");
        } else {
            System.out.println("a對象尚未被回收" + wrA.get());
        }

        System.out.println("rq item:" + rq.poll());
        // 垃圾回收
        System.gc();
        if (wrA.get() == null) {
            System.out.println("a對象進入垃圾回收流程");
        } else {
            System.out.println("a對象尚未被回收" + wrA.get());
        }
        /*
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        */
        System.out.println("rq item:" + rq.poll());
    }
}

class A {

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("in A finalize");
    }

}

輸出結果爲:

a對象尚未被回收A@302b2c81
rq item:null
a對象進入垃圾回收流程
rq item:null
in A finalize

由此,驗證了“僅進入垃圾回收流程的SoftReference或WeakReference引用尚未被加入到ReferenceQueue”。

public class ReferenceTest {
    public static void main(String[] args) {
        A a = new A();
        ReferenceQueue<A> rq = new ReferenceQueue<A>();
        WeakReference<A> wrA = new WeakReference<A>(a, rq);
        a = null;
        if (wrA.get() == null) {
            System.out.println("a對象進入垃圾回收流程");
        } else {
            System.out.println("a對象尚未被回收" + wrA.get());
        }
        System.out.println("rq item:" + rq.poll());
        // 垃圾回收
        System.gc();
        if (wrA.get() == null) {
            System.out.println("a對象進入垃圾回收流程");
        } else {
            System.out.println("a對象尚未被回收" + wrA.get());
        }
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("rq item:" + rq.poll());
    }
}

class A {
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("in A finalize");
    }
}

輸出結果爲:

a對象尚未被回收A@6276e1db
rq item:null
a對象進入垃圾回收流程
in A finalize
rq item:java.lang.ref.WeakReference@645064f
由此,證實了上述說法。

虛引用(PhantomReference)
“虛引用”顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用並不會決定對象的生命週期。如果一個對象僅持有虛引用,那麼它就和沒有任何引用一樣, 在任何時候都可能被垃圾回收器回收。

虛引用主要用來跟蹤對象被垃圾回收器回收的活動。虛引用與軟引用和弱引用的一個區別在於:虛引用必須和引用隊列(ReferenceQueue)聯合使用。 當垃圾回收器準備回收一個對象時,如果發現它還有虛引用,就會在回收對象的內存之前,把這個虛引用加入到與之 關聯的引用隊列中。

與SoftReference或WeakReference相比,PhantomReference主要差別體現在如下幾點:

1.PhantomReference只有一個構造函數PhantomReference(T referent, ReferenceQueue

prA.get():null
rq item:java.lang.ref.PhantomReference@1da12fc0

代碼中的Thread.sleep(1);作用與上例中相同,都是確保垃圾回收線程能夠執行。否則,進進入垃圾回收流程而沒有真正被垃圾回收 的指示對象的虛引用是不會被加入到PhantomReference中的。

與WeakReference相同,PhantomReference並不會改變其指示對象的垃圾回收時機。且可以總結出:ReferenceQueue的作用主要是用於監聽SoftReference /WeakReference/PhantomReference的指示對象是否已經被垃圾回收。

Reference
Reference內部通過一個 {Reference} next 的字段來構建一個Reference類型的單向鏈表。另外其內部還包含一個 ReferenceQueue

public void run() {
        for (;;) {

        Reference r;
        synchronized (lock) {
        // 檢查pending是否爲null
            if (pending != null) {
            r = pending;
            Reference rn = r.next;
            pending = (rn == r) ? null : rn;
            r.next = r;
            } else {
            try {
          // pending爲null時,則將當前線程進入wait set,等待GC執行後執行notifyAll
                lock.wait();
            } catch (InterruptedException x) { }
            continue;
            }
        }

        // Fast path for cleaners
        if (r instanceof Cleaner) {
            ((Cleaner)r).clean();
            continue;
        }
        // 追加到對應的引用隊列中
        ReferenceQueue q = r.queue;
        if (q != ReferenceQueue.NULL) q.enqueue(r);
        }
    }

注意:由於通過靜態代碼塊進行線程的創建和啓動,因此Reference的所有子類實例均通過同一個線程進行向各自的引用隊列追加引用對象的操作。

WeakHashMap
由於WeakHashMap的鍵對象爲弱引用,因此當發生GC時鍵對象所指向的內存空間將被回收,被回收後再調用size、clear或put等直 接或間接調用私有expungeStaleEntries方法的實例方法時,則這些鍵對象已被回收的項目(Entry)將被移除出鍵值對集合中。

下列代碼將發生OOM

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

        List<WeakHashMap<byte[][], byte[][]>> maps = new ArrayList<WeakHashMap<byte[][], byte[][]>>();

        for (int i = 0; i < 1000; i++) {
            WeakHashMap<byte[][], byte[][]> d = new WeakHashMap<byte[][], byte[][]>();
            d.put(new byte[1000][1000], new byte[1000][1000]);
            maps.add(d);
            System.gc();
            System.err.println(i);
        }
    }

而下面的代碼因爲集合的Entry被移除因此不會發生OOM

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

        List<WeakHashMap<byte[][], byte[][]>> maps = new ArrayList<WeakHashMap<byte[][], byte[][]>>();

        for (int i = 0; i < 1000; i++) {
            WeakHashMap<byte[][], byte[][]> d = new WeakHashMap<byte[][], byte[][]>();
            d.put(new byte[1000][1000], new byte[1000][1000]);
            maps.add(d);
            System.gc();
            System.err.println(i);

            for (int j = 0; j < i; j++) {
                // 觸發移除Entry操作
                System.err.println(j+  " size" + maps.get(j).size());
            }
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章