天天看點

Java 四種引用類型完全解析

前言

很早就聽說有這些引用,從來沒用過,最近看源碼發現,内部用到的地方挺多,就看了一下相關的文檔。

強引用,軟引用,弱引用,虛弱引用四種引用類型來友善來管理對象的生命周期。

簡單比喻:

1.保潔(GC),日用品(Strong Reference),一次性用品的(soft Reference),生活垃圾(weak Reference),灰塵(PhantomReference)

Gc 一次,并不會清掃日用品,如果空間較為擁擠,則會清掃一次性用品,每次GC,均會清掃生活垃圾,
 如果判斷是否要進行,Gc的标準,是判斷灰塵是否已經被清掃。      

PhantomReference,Abstract Reference,ReferenceQueue,SoftReference,WeakReference.

ReferenceQueue ,單連結清單結構,存儲被持有回收對象的引用類型,可以和四種引用類型組合使用,而虛引用則必須和ReferenceQueue關聯使用

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對象,并使棧記憶體中的srA指向此對象。

此時,進行如下編碼: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@4807ccf6
2 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@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();
 
         //...
 
     }
 
 }      

在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 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中。

看下面的一個例子:

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");
     }
 
 }      

輸出結果為:

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

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

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 {
 
 }      

輸出結果為:

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