四大引用
Java提供了四種級(jí)別的應(yīng)用類型:強(qiáng)引用、軟引用、弱引用及虛引用。那么這四種引用類型分別有什么作用,又有什么區(qū)別呢?
強(qiáng)引用(StrongReference)
強(qiáng)引用是我們最常用的一種引用類型。當(dāng)我們使用new關(guān)鍵字去新建一個(gè)對象的時(shí)候,創(chuàng)建的就是強(qiáng)引用。比如:
Obejct object = new Obejct();
強(qiáng)引用有如下特點(diǎn):
- 只要強(qiáng)引用存在,垃圾收集器永遠(yuǎn)不會(huì)回收掉被引用的對象。
- 強(qiáng)引用可能會(huì)導(dǎo)致內(nèi)存泄漏
- JVM寧愿拋出OOM異常,也不會(huì)回收強(qiáng)引用。
強(qiáng)引用與Android開發(fā)中的OOM異常
OOM異常是Android開發(fā)過程中很常見的一種異常,下面來看一個(gè)例子:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new MyAsyncTask(this).execute();
}
//非靜態(tài)內(nèi)部類,在MyAsyncTask的整個(gè)生命周期內(nèi)都會(huì)持有對外部類MainActivity的強(qiáng)引用
private class MyAsyncTask extends AsyncTask {
MainActivity mActivity;
public MyAsyncTask(MainActivity mainActivity) {
mActivity=mainActivity;
}
@Override
protected Object doInBackground(Object[] params) {
// 模擬耗時(shí)任務(wù)
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return doSomething();
}
private Object doSomething() {
return new Object();
}
@Override
protected void onPostExecute(Object object) {
super.onPostExecute(object);
// 假如在下方有更新UI的代碼....
}
}
}
簡單的解釋一下上面這段代碼:MyAsyncTask會(huì)跟隨Activity的onCreate去創(chuàng)建并開始執(zhí)行一個(gè)長時(shí)間的耗時(shí)任務(wù),并在耗時(shí)任務(wù)完成后去更新MainActivity中的UI。這是一個(gè)很常見的使用場景,卻會(huì)導(dǎo)致內(nèi)存泄露問題:
但是,在Java中,非靜態(tài)內(nèi)部類會(huì)在其整個(gè)生命周期中持有對它外部類的強(qiáng)引用。
所以,當(dāng)MainActivity被銷毀時(shí),MyAsyncTask中的耗時(shí)任務(wù)可能仍沒有執(zhí)行完成,MyAsyncTask則會(huì)一直存活。此時(shí),由于MyAsyncTask持有著其外部類,即MainActivity的引用,將導(dǎo)致MainActivity不能被垃圾回收,這就會(huì)導(dǎo)致內(nèi)存泄漏。如果MainActivity中還持有著Bitmap等大對象,反復(fù)進(jìn)出這個(gè)頁面幾次可能就會(huì)出現(xiàn)OOM了。
那么我們?nèi)绾伪苊膺@樣的問題出現(xiàn)呢?接下來就該弱引用出場了。
弱引用(WeakReference )
- 被弱引用關(guān)聯(lián)的對象只能生產(chǎn)到下一次垃圾收集發(fā)生之前。當(dāng)垃圾收集器工作室,無論內(nèi)存是否足夠,都會(huì)回收弱引用關(guān)聯(lián)的對象。
- 可以使用WeakReference類來實(shí)現(xiàn)弱引用
使用弱引用避免OOM
接下來,我們看看如何使用弱引用改造上面的例子:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new MyAsyncTask(this).execute();
}
//MyAsyncTask改為了靜態(tài)內(nèi)部類
private static class MyAsyncTask extends AsyncTask {
//對外部類MainActivity的引用換成弱引用
private WeakReference<MainActivity> mActivity;
public MyAsyncTask(MainActivity mainActivity) {
mActivity= new WeakReference<>(mainActivity);
}
@Override
protected Object doInBackground(Object[] params) {
// 模擬耗時(shí)任務(wù)
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return doSomething();
}
private Object doSomething() {
return new Object();
}
@Override
protected void onPostExecute(Object object) {
super.onPostExecute(object);
//判空
if (mActivity.get()!=null){
// 假如在下方有更新UI的代碼....
}
}
}
}
從代碼中可以看出,我們把MyAsyncTask改為了靜態(tài)內(nèi)部類,并且其對外部類MainActivity的引用換成了弱引用。
修改之后,當(dāng)MainActivity destroy的時(shí)候,由于MyAsyncTask是通過弱引用的方式持有MainActivity,所以并不會(huì)阻止MainActivity被垃圾回收器回收,也就不會(huì)有內(nèi)存泄露產(chǎn)生了。
弱引用的其他應(yīng)用
-
內(nèi)存泄漏監(jiān)測框架LeakCanary
弱引用與ReferenceQueue聯(lián)合使用。ReferenceQueue隊(duì)列的具體作用就是當(dāng)發(fā)生 GC 后,弱引用所持有的對象如果被回收就會(huì)進(jìn)入該隊(duì)列,所以只要在 activity onDestory 時(shí),把 Activity 對象綁定在 WeakReference 中,然后手動(dòng)執(zhí)行一次 GC,然后觀察 ReferenceQuery 中是不是包含對應(yīng)的 Activity 對象,如果不包含,說明 Activity 被強(qiáng)引用,也就是發(fā)生了內(nèi)存泄漏。
WeakHashMap
當(dāng)key只有弱引用時(shí),GC發(fā)現(xiàn)后會(huì)自動(dòng)清理鍵和值,作為簡單的緩存表解決方案。ThreadLocal
ThreadLocal.ThreadLocalMap.Entry 繼承了弱引用,key為當(dāng)前線程實(shí)例,和WeakHashMap基本相同。
軟引用(SoftReference)
對于軟引用關(guān)聯(lián)著的對象,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前,將會(huì)把這些對象列進(jìn)回收范圍并進(jìn)行二次回收。
弱引用與軟引用的根本區(qū)別在于:只具有弱引用的對象擁有更短暫的生命周期,可能隨時(shí)被回收。而只具有軟引用的對象只有當(dāng)內(nèi)存不夠的時(shí)候才被回收,在內(nèi)存足夠的時(shí)候,通常不被回收。
從引用的強(qiáng)度來講: 強(qiáng)引用 > 軟引用 > 弱引用。
-
簡單的使用例子:
//軟引用的簡單使用 News news = new News(); SoftReference softReference = new SoftReference(news); News news = (News) softReference.get();
軟引用的應(yīng)用
- 從網(wǎng)絡(luò)上獲取圖片,可以通過軟引用緩存起來。但是,在實(shí)踐中,使用軟引用作為緩存時(shí)效率是比較低的,系統(tǒng)并不知道哪些軟引用指向的對象應(yīng)該被回收,哪些應(yīng)該被保留。過早被回收的對象會(huì)導(dǎo)致不必要的工作,比如Bitmap要重新從SdCard或者網(wǎng)絡(luò)上加載到內(nèi)存。在Android開發(fā)中,一種更好的選擇是使用LruCache。
虛引用(PhantomReference)
- 也稱為幽靈引用或者幻影引用,它是最弱的一種引用關(guān)系。
- 一個(gè)對象是否有虛引用的存在,完全不會(huì)對其生存時(shí)間構(gòu)成影響,也無法通過虛引用來取得一個(gè)對象實(shí)例。
- 可以使用PhantomReference類來實(shí)現(xiàn)虛引用。
- 應(yīng)用:
- 為一個(gè)對象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是希望能在這個(gè)對象被收集器回收時(shí)收到一個(gè)系統(tǒng)通知。