Android中的強(qiáng)引用、軟引用、弱引用和虛引用你真的都懂了嘛?

四大引用

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)通知。

對比總結(jié)

對比不同

img

參考:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容