Java/Android中有四種參考型別,分別是:
Strong reference - 強引用
Soft Reference - 軟引用
Weak Reference - 弱引用
Phantom Reference - 虛引用
不同的參考型別有著不同的特性,同時也對應著不同的使用情境。
1.Strong reference - 強引用
實際編碼中最常見的一種參考型別。常見形式如:A a = new A();等。強引用本身儲存在棧記憶體中,其儲存指向對記憶體中對象的地址。一般情況下,當對記憶體中的對象不再有任何強引用指向它時,記憶體回收機器開始考慮可能要對此記憶體進行的記憶體回收。如當進行編碼:a = null,此時,剛剛在堆中分配地址並建立的a對象沒有其他的任何引用,當系統進行記憶體回收時,堆記憶體將被記憶體回收。
SoftReference、WeakReference、PhantomReference都是類java.lang.ref.Reference的子類。Reference作為抽象基類,定義了其子類對象的基本操作。Reference子類都具有如下特點:
1.Reference子類不能無參化直接建立,必須至少以強引用對象為構造參數,建立各自的子類對象;
2.因為1中以強引用對象為構造參數建立對象,因此,使得原本強引用所指向的堆記憶體中的對象將不再只與強引用本身直接關聯,與Reference的子類對象的引用也有一定聯絡。且此種聯絡將可能影響到對象的記憶體回收。
根據不同的子類對象對其指示對象(強引用所指向的堆記憶體中的對象)的記憶體回收不同的影響特點,分別形成了三個子類,即SoftReference、WeakReference和PhantomReference。
2.Soft Reference - 軟引用
軟引用的一般使用形式如下:
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 {}
##輸出結果為:
1 a對象尚未被回收A@4807ccf62 a對象尚未被回收A@4807ccf6
當 a = null後,堆記憶體中的A對象將不再有任何的強引用指向它,但此時尚存在srA引用的對象指向A對象。當第一次調用srA.get()方法返回此指示對象時,由於記憶體回收行程很有可能尚未進行記憶體回收,此時get()是有結果的,這個很好理解。當程式執行System.gc();強制記憶體回收後,通過srA.get(),發現依然可以得到所指示的A對象,說明A對象並未被記憶體回收。那麼,軟引用所指示的對象什麼時候才開始被記憶體回收呢?需要滿足如下兩個條件:
1.當其指示的對象沒有任何強引用對象指向它;
2.當虛擬機器記憶體不足時。
因此,SoftReference變相的延長了其指示對象佔據堆記憶體的時間,直到虛擬機器記憶體不足時記憶體回收行程才回收此堆記憶體空間。
3.Weak Reference - 弱引用
同樣的,軟引用的一般使用形式如下:
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@52e5376aa對象進入記憶體回收流程
輸出的第一條結果解釋同上。當進行記憶體回收後,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(); //... }}
在Eclispe中Run Link,將會看到警示資訊: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 classId: HandlerLeakSince 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<T>,當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"); }}
##輸出結果為:
1 a對象尚未被回收A@46993aaa2 a對象被回收3 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@302b2c81rq item:nulla對象進入記憶體回收流程rq item:nullin 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@6276e1dbrq item:nulla對象進入記憶體回收流程in A finalizerq item:java.lang.ref.WeakReference@645064f
由此,證實了上述說法。
4.PhantomReference
與SoftReference或WeakReference相比,PhantomReference主要差別體現在如下幾點:
1.PhantomReference只有一個建構函式PhantomReference(T referent, ReferenceQueue<? super T> q),因此,PhantomReference使用必須結合ReferenceQueue;
2.不管有無強引用指向PhantomReference的指示對象,PhantomReference的get()方法返回結果都是null。
public class ReferenceTest { public static void main(String[] args) { A a = new A(); ReferenceQueue<A> rq = new ReferenceQueue<A>(); PhantomReference<A> prA = new PhantomReference<A>(a, rq); System.out.println("prA.get():" + prA.get()); a = null; System.gc(); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("rq item:" + rq.poll()); }}class A {}
##輸出結果為:
prA.get():nullrq item:java.lang.ref.PhantomReference@1da12fc0
代碼中的Thread.sleep(1);作用與上例中相同,都是確保記憶體回收線程能夠執行。否則,進進入記憶體回收流程而沒有真正被記憶體回收的指示對象的虛引用是不會被加入到PhantomReference中的。
與WeakReference相同,PhantomReference並不會改變其指示對象的記憶體回收時機。且可以總結出:ReferenceQueue的作用主要是用於監聽SoftReference/WeakReference/PhantomReference的指示對象是否已經被記憶體回收。
以上就是小編為大家帶來的Java/Android參考型別及其使用全面分析的全部內容了,希望對大家有所協助,多多支援雲棲社區~