Android內存泄漏分享

內容概述

  1. 內存泄漏和內存管理相關基礎。
  2. Android中的內存使用。
  3. 內存分析工具和實踐。

如下內容不考慮非引用類型的數據,或者將其等同爲對應的引用類型看待——一切皆對象java

內存泄漏概念

再也不使用的對象常駐內存,如靜態變量,或被其它還在使用的對象(生命週期更長)所引用的對象,對應內存沒法回收利用。android

爲了不對象沒法正確、及時被釋放,須要理解:
GC如何回收對象,如何釋放對象?緩存

對象的引用

對象的使用是經過指向它的引用被訪問的,引用被保存在引用類型變量中。網絡

這裏變量指:dom

類變量:靜態成員變量,成員變量也叫字段。
實例變量:非靜態成員變量。
局部變量:在方法中定義,賦值和使用。
不考慮:參數、返回值、常量。異步

在new一個對象後,其強引用被構造方法返回。
對象的內部類對象,也擁有this$0這樣的強引用指向它。ide

Java有四種引用,分別對應不一樣性質的引用可達性(reachable)——可達指經過此引用訪問到對應的對象。

引用的分類

強引用使用引用對應的類型變量保存,須要手動釋放——設置引用變量爲null。
java的有四種引用,其它三種引用由對應的引用包裹類實現——能夠認爲是特殊類型的引用變量,GC在對待這些引用變量時有不一樣的策略:工具

  • 強引用(StrongReference)
    正常聲明的變量都是強引用,即使拋出OutOfMemoryError異常,也不會被回收。須要手動設置變量爲null來釋放引用。學習

  • 軟引用(SoftReference)
    僅在內存不足時GC纔會回收軟引用對象。測試

  • 弱引用(WeakReference)
    一旦掃描到對象僅擁有弱引用,就回收。GC運行在一個優先級很低的線程,不會那麼「及時」發現。

在經過引用包裹對象get得到實際對象時,有可能爲null。可使用一個ReferenceQueue來關聯軟引用和弱引用對象,它們在回收時其引用包裹對象被添加至此隊列。

  • 虛引用(PhantomReference)
    不對GC產生任何影響,必須有關聯的ReferenceQueue,在對象僅剩虛引用時,GC在回收它時把對應的引用包裹對象放入ReferenceQueue——一般就是用來跟蹤對象的回收,作一些清理操做。不含任何「持有」對象的概念,get永遠返回null。

GC什麼時候回收對象[簡要]

Java有自動的內存回收機制,在合適的時候,運行時會執行GC來清理掉那些再也不被使用的對象。根據內存須要,程序運行時會不按期屢次執行GC。

Java判斷對象是否再也不使用有多種策略,最終都是和對象的引用相關。

若是對象的引用數量爲0,那麼它顯然是垃圾對象。
此外,Java使用「根對象可達性」來斷定對象是否有效。

在虛擬機中,有一類GC相關的對象被稱做「GC root」。
GC root經過引用變量一級級來找到堆中的每個對象。很顯然,不一樣類型的引用變量,GC對待它們有不一樣的發現(使用其中的引用)策略。
那些最終不能從根對象引用獲得的對象被認爲是不可達對象,也就是可回收對象。

可見,只有強引用須要咱們本身來考慮其釋放的問題。在分析內存泄漏問題時,咱們主要關注對象的強引用。

對象的釋放

對象的釋放,就是對其強引用的釋放——將保存此引用的變量設置爲null。另外,若對象包含內部類對象,那麼內部對象的引用也要被釋放。

不一樣的變量它們的默認生命週期是不同的。

  1. 非靜態成員變量隨對象的釋放而釋放
  2. 局部變量隨方法結束釋放
  3. 靜態成員變量隨進程結束而釋放。

均可以「手動」設置爲null來釋放。

方法未返回前,執行域的變量都不會釋放。須要注意一些方法中的變量的及時釋放。

void releaseObject() {
    Person p = new Person();
    p = new Person(); // 釋放
    p = null;         // 釋放
    // more code...
}

void uglyMethod() {
    Task task;
    while(!stop) {
        task = mBlockingQueue.take(); // 阻塞
        
        //一些針對task的操做。
    }
}
上面,在take()得到下一個對象賦給task以前,task一直引用着上一個從隊列中得到的Task對象——它沒法被釋放。

引用的方向

引用指向某個對象。
A持有B的引用,那麼此引用的方向從A到B。
A不可釋放,A引用B,那麼B也不可釋放。反之,B引用到了不可釋放的A,對B的釋放沒有影響。

Outgoing Reference:
對於一個對象,查看它擁有的引用變量,能夠知道它所引用的其它對象。

Incoming Reference:
其它對象持有的指向當前對象的引用變量。

環引用

若A和B互相引用,這兩個對象則造成一個環形引用,但不是根對象可達,環形引用是能夠被正常回收的。

Android中的內存使用

  • Android程序有內存限制。
  • 頻繁的GC容易形成程序響應問題。
  • 進程自動回收:運行在後臺的程序,擁有的內存越大,越容易被回收——任務棧和進程的關係——作好數據持久化、程序狀態連續性和恢復。

對象使用的建議

Android程序偏向更輕量級的對象,更少的內存佔用時間(除去必要的內存緩存),重用避免重複建立。

  • 避免使用枚舉
    使用final static int。

  • 多使用final修飾
    除非業務須要,首選final修飾,編譯器會優化。

  • 圖片
    成熟的庫(Android-Universal-Image-Loader),用多少取多少,及時釋放,緩存。

  • 軟引用和弱引用
    能知足須要的話,代替強引用。

  • 池和對象複用
    避免對象建立,引發內存抖動。例如知道一個集合是固定大小的話,那麼每次網絡請求結束後更新對象字段值,而不是clear又建立一批新對象。
    線程池——好處很少說。使用時注意由於run持久不結束,線程對象對應的字段和局部變量注意泄漏。
    Adapter中數據對象和View的複用。

  • UI操做的去噪
    快速滑動、輸入等。

  • 避免沒必要要的getter、setter
    僅僅是簡單的POJO,徹底不必訪問控制器。
  • 合併handler
    handler不要離開Activity,最好的一個Activity使用一個就夠了。不要使用Handler代替回調來通訊,使用第三方庫,如EventBus來解耦,handler傳遞數據很低效(不及時-它不是同步的,對象序列化)。
    handler是用來完成跨線程的通訊的。
  • 及時釋放引用
    能使用局部變量的,就不要使用字段。方法中,釋放那些不使用又繼續佔有的對象引用。
    四大組件對象不是由咱們new的,有其明確的生命週期,在「銷燬」動做時從對象引用層面釋放該釋放的。
  • 內部類
    優先使用靜態內部類。
    匿名內部類老是默認持有外部類對象的引用。

  • 在保證速度的前提下使用文件緩存
    一些狀況下甚至是必須的,如登陸狀態。
  • 使用ApplicationContext
    僅在必要的時候——如dialog——使用Activity,並且注意Activity的Context的及時釋放。

  • 使用具體類而不是接口
    例如,HashMap,變量不須要聲明爲Map,這會有更好的執行速度。
    不必爲「不存在」的擴展性作犧牲。

  • 在onDestroy中作好清理
    主要是引用的釋放,廣播的取消註冊,回調/監聽對象的解除,handler的取消投遞的消息、網絡請求的取消、動畫的中止,線程、其它異步任務和處理等。

「最佳實踐」平時多收集,原則上
對於泄漏問題,只有一點,不使用就及時把保持引用的成員變量和局部變量設置爲null。重點注意回調和靜態字段。

常見的泄漏

典型大對象

  • Activity
  • 圖片、音頻、視頻文件
  • Json數據

能夠從Activity開始,依次排查佔用內存較大的對象的泄漏。一般,一個包含更多其它對象的大對象的釋放,順帶解決了不少對象的泄漏。

匿名內部類

網絡,語音,線程,其它異步操做,若是使用到callBack/Listener對象,應該注意這些對象的釋放。
場景:
AudioManager是全局的語音管理對象。
假設播放須要傳遞語音文件路徑並提供回調來控制UI:
在Activity中:

void onCreate() {
    AudioManager.addListener(new AudioPlayCallBack() {
        @Override
        public void startPlay() {
        }
        
        @Override
        public void stopPlay() {
        }
    });
}

void onPlayButtonClick() {
    AudioManager.startPlay(mAudioPath);
}

void onDestroy() {
    AudioManager.clearListener();
    AudioManager.stopPlay();
}

監聽(觀察者模式),廣播接收器

同回調同樣,通常的,Activity中使用Receiver或Observer對象,在onCreate中開始註冊,在onDestroy中須要解除註冊。

Service

做爲四大組件之一,對象自己建立和釋放不是咱們控制。使用startService和stopService、bingService和unBindService來控制組件對象的生命週期。
一般服務是一直運行在後臺的,避免在服務中保存不使用的對象。
場景:

ServiceConnection conn = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mCoreService = ((CoreService.CoreServiceBinder) service).getService();
            mCoreService.registerConnectionStatusCallback(new IConnectionStatusCallback() {
                @Override
                public void connectionStatusChanged(int connectedState, String reason) {
                    // xxx
                }
            });
            // xxx
        }

        /**
        在和Service的鏈接「意外」中斷時執行,一般是運行Service的其它進程崩潰後引發。
        同一進程中幾乎不會發生(Service死掉了,而此處代碼還在執行...):此方法幾乎不會被執行。
        不會移除此鏈接。必須主動調用unbindService來解除鏈接。
        */
        @Override
        public void onServiceDisconnected(ComponentName name) {
            mCoreService.unRegisterConnectionStatusCallback(); // 不執行
            mCoreService = null;// 不執行
        }
    };

上面應該在onDestroy中unbindService並移除Activity和Service對象的引用(回調匿名內部類)鏈接。

Handler

延遲消息被線程中的MessageQueue持有,在消息未處理前,Message對象引用handler,而handler引用Activity的事情很容易發生。

handler大多數時間也是寫爲匿名內部類——這自己沒什麼。
在onDestroy中:

void onDestroy() {
    handler.removeCallbacksAndMessages(null);
}

Context

Android中Context是「God Object」,它擁有不少運行時須要的全局信息。一般使用第三方庫,系統API時,須要一個
Context時,優先使用Application。若是必須用到Activity的狀況,記得它和匿名內部類是同樣的,不要在三番五次的參數傳遞以後,忘記釋放。

動畫

屬性動畫必須手動stop,不然它會一直執行下去,持有Activity的mContext致使Activity對象的泄漏。

單例、全局對象

少用,注意意外的引用駐留。
簡單的:
ActivityManager管理Activity的集合,在onCreate和onDestroy時從ActivityManager中add和remove掉。
類變量若是是內部類這樣的擁有對外部類的引用:
記得釋放類變量,或者換用靜態內部類,普通類,而後提供對外部類引用的設置和解除。

總而言之:對象是有生命週期的,須要在合適的時間釋放對象的強引用。

內存分析工具

學習內存分析工具的使用,在實踐中積累內存泄漏的問題,避免錯誤的代碼。

Android Monitor

Android Studio 1.5以上版本有此功能。
能夠快速查看對象個數,佔用內存狀況,「簡單地」分析對象引用狀況。

Memory Analysis Tool

Java的內存分析工具。

Shallow vs. Retained Heap

Dominator Tree

LeakCanary

產生和發現引用泄漏

檢測目標類

運行程序,GC後dump生成hprof文件,使用MAT分析。

全面測試

在測試環境,使用LeakCanary實時監測。

討論

列表+詳情頁 詳情頁 使用SingleInstance?不容許多個頁面同時打開?

Volley Dispatcher引發的泄露。

收集踩過的坑。

相關文章
相關標籤/搜索