Java/Android引用類型及其使用全面分析
Java/Android中有四種引用類型,分別是:
Strong reference - 強引用
Soft Reference - 軟引用
Weak Reference - 弱引用
Phantom Reference - 虛引用
不同的引用類型有著不同的特性,同時也對應著不同的使用場景。
1.Strong reference - 強引用
實際編碼中最常見的一種引用類型。常見形式如:A a = new A();等。強引用本身存儲在棧內(nèi)存中,其存儲指向?qū)?nèi)存中對象的地址。一般情況下,當對內(nèi)存中的對象不再有任何強引用指向它時,垃圾回收機器開始考慮可能要對此內(nèi)存進行的垃圾回收。如當進行編碼:a = null,此時,剛剛在堆中分配地址并新建的a對象沒有其他的任何引用,當系統(tǒng)進行垃圾回收時,堆內(nèi)存將被垃圾回收。
SoftReference、WeakReference、PhantomReference都是類java.lang.ref.Reference的子類。Reference作為抽象基類,定義了其子類對象的基本操作。Reference子類都具有如下特點:
1.Reference子類不能無參化直接創(chuàng)建,必須至少以強引用對象為構造參數(shù),創(chuàng)建各自的子類對象;
2.因為1中以強引用對象為構造參數(shù)創(chuàng)建對象,因此,使得原本強引用所指向的堆內(nèi)存中的對象將不再只與強引用本身直接關聯(lián),與Reference的子類對象的引用也有一定聯(lián)系。且此種聯(lián)系將可能影響到對象的垃圾回收。
根據(jù)不同的子類對象對其指示對象(強引用所指向的堆內(nèi)存中的對象)的垃圾回收不同的影響特點,分別形成了三個子類,即SoftReference、WeakReference和PhantomReference。
2.Soft Reference - 軟引用
軟引用的一般使用形式如下:
A a = new A();
SoftReference<A> srA = new SoftReference<A>(a);
通過對象的強引用為參數(shù),創(chuàng)建了一個SoftReference對象,并使棧內(nèi)存中的wrA指向此對象。
此時,進行如下編碼:a = null,對于原本a所指向的A對象的垃圾回收有什么影響呢?
先直接看一下下面一段程序的輸出結(jié)果:
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 {
}
##輸出結(jié)果為:
1 a對象尚未被回收A@4807ccf6 2 a對象尚未被回收A@4807ccf6
當 a = null后,堆內(nèi)存中的A對象將不再有任何的強引用指向它,但此時尚存在srA引用的對象指向A對象。當?shù)谝淮握{(diào)用srA.get()方法返回此指示對象時,由于垃圾回收器很有可能尚未進行垃圾回收,此時get()是有結(jié)果的,這個很好理解。當程序執(zhí)行System.gc();強制垃圾回收后,通過srA.get(),發(fā)現(xiàn)依然可以得到所指示的A對象,說明A對象并未被垃圾回收。那么,軟引用所指示的對象什么時候才開始被垃圾回收呢?需要滿足如下兩個條件:
1.當其指示的對象沒有任何強引用對象指向它;
2.當虛擬機內(nèi)存不足時。
因此,SoftReference變相的延長了其指示對象占據(jù)堆內(nèi)存的時間,直到虛擬機內(nèi)存不足時垃圾回收器才回收此堆內(nèi)存空間。
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 {
}
##輸出結(jié)果為:
a對象尚未被回收A@52e5376a a對象進入垃圾回收流程
輸出的第一條結(jié)果解釋同上。當進行垃圾回收后,wrA.get()將返回null,表明其指示對象進入到了垃圾回收過程中。因此,對弱引用特點總結(jié)為:
WeakReference不改變原有強引用對象的垃圾回收時機,一旦其指示對象沒有任何強引用對象時,此對象即進入正常的垃圾回收流程。
那么,依據(jù)此特點,很可能有疑問:WeakReference存在又有什么意義呢?
其主要使用場景見于:當前已有強引用指向強引用對象,此時由于業(yè)務需要,需要增加對此對象的引用,同時又不希望改變此引用的垃圾回收時機,此時WeakReference正好符合需求,常見于一些與生命周期的場景中。
下面給出一個Android中關于WeakReference使用的場景 —— 結(jié)合靜態(tài)內(nèi)部類和WeakReference來解決Activity中可能存在的Handler內(nèi)存泄露問題。
Activity中我們需要新建一個線程獲取數(shù)據(jù),使用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定義成內(nèi)部靜態(tài)類,并在此靜態(tài)內(nèi)部類中定義一個WeakReference的引用,由于指示外部的Activity對象。
問題分析:
Activity具有自身的生命周期,Activity中新開啟的線程運行過程中,可能此時用戶按下了Back鍵,或系統(tǒng)內(nèi)存不足等希望回收此Activity,由于Activity中新起的線程并不會遵循Activity本身的什么周期,也就是說,當Activity執(zhí)行了onDestroy,由于線程以及Handler 的HandleMessage的存在,使得系統(tǒng)本希望進行此Activity內(nèi)存回收不能實現(xiàn),因為非靜態(tài)內(nèi)部類中隱性的持有對外部類的引用,導致可能存在的內(nèi)存泄露問題。
因此,在Activity中使用Handler時,一方面需要將其定義為靜態(tài)內(nèi)部類形式,這樣可以使其與外部類(Activity)解耦,不再持有外部類的引用,同時由于Handler中的handlerMessage一般都會多少需要訪問或修改Activity的屬性,此時,需要在Handler內(nèi)部定義指向此Activity的WeakReference,使其不會影響到Activity的內(nèi)存回收同時,可以在正常情況下訪問到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,還有一個構造器參數(shù)為ReferenceQueue<T>,當SoftReference或WeakReference所指示的對象確實被垃圾回收后,其引用將被放置于ReferenceQueue中。注意上文中,當SoftReference或WeakReference的get()方法返回null時,僅是表明其指示的對象已經(jīng)進入垃圾回收流程,此時對象不一定已經(jīng)被垃圾回收。而只有確認被垃圾回收后,如果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");
}
}
##輸出結(jié)果為:
1 a對象尚未被回收A@46993aaa 2 a對象被回收 3 in A finalize
由此,也驗證了上文中的“進入垃圾回收流程”的說法。下面結(jié)合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");
}
}
##輸出結(jié)果為:
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");
}
}
##輸出結(jié)果為:
a對象尚未被回收A@6276e1db rq item:null a對象進入垃圾回收流程 in A finalize rq item:java.lang.ref.WeakReference@645064f
由此,證實了上述說法。
4.PhantomReference
與SoftReference或WeakReference相比,PhantomReference主要差別體現(xiàn)在如下幾點:
1.PhantomReference只有一個構造函數(shù)PhantomReference(T referent, ReferenceQueue<? super T> q),因此,PhantomReference使用必須結(jié)合ReferenceQueue;
2.不管有無強引用指向PhantomReference的指示對象,PhantomReference的get()方法返回結(jié)果都是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 {
}
##輸出結(jié)果為:
prA.get():null rq item:java.lang.ref.PhantomReference@1da12fc0
代碼中的Thread.sleep(1);作用與上例中相同,都是確保垃圾回收線程能夠執(zhí)行。否則,進進入垃圾回收流程而沒有真正被垃圾回收的指示對象的虛引用是不會被加入到PhantomReference中的。
與WeakReference相同,PhantomReference并不會改變其指示對象的垃圾回收時機。且可以總結(jié)出:ReferenceQueue的作用主要是用于監(jiān)聽SoftReference/WeakReference/PhantomReference的指示對象是否已經(jīng)被垃圾回收。
以上就是小編為大家?guī)淼腏ava/Android引用類型及其使用全面分析的全部內(nèi)容了,希望對大家有所幫助,多多支持腳本之家~
相關文章
Java數(shù)據(jù)結(jié)構之散列表(動力節(jié)點Java學院整理)
散列表(Hash table,也叫哈希表),是根據(jù)關鍵字(key value)而直接進行訪問的數(shù)據(jù)結(jié)構。這篇文章給大家介紹了java數(shù)據(jù)結(jié)構之散列表,包括基本概念和散列函數(shù)相關知識,需要的的朋友參考下吧2017-04-04
學習Java之如何正確地向上轉(zhuǎn)型與向下轉(zhuǎn)型
面向?qū)ο蟮牡谌齻€特征是多態(tài),實現(xiàn)多態(tài)有三個必要條件:繼承、方法重寫和向上轉(zhuǎn)型,在學習多態(tài)之前,我們還要先學習Java的類型轉(zhuǎn)換,本篇文章就來帶大家認識什么是類型轉(zhuǎn)換,看看類型轉(zhuǎn)換都有哪幾種情況,以及如何避免類型轉(zhuǎn)換時出現(xiàn)異常2023-05-05
Spring ApplicationListener源碼解析
這篇文章主要為大家介紹了Spring ApplicationListener源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-01-01
springboot實現(xiàn)rabbitmq消息確認的示例代碼
RabbitMQ的消息確認有兩種, 一種是消息發(fā)送確認,第二種是消費接收確認,本文主要介紹了springboot實現(xiàn)rabbitmq消息確認的示例代碼,具有一定的參考價值,感興趣的可以了解一下2023-09-09
Mybatis-plus實現(xiàn)主鍵自增和自動注入時間的示例代碼
這篇文章主要介紹了Mybatis-plus實現(xiàn)主鍵自增和自動注入時間的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-07-07

