內存泄露從入門到精通三部曲之常見緣由與用戶實踐

內存泄露從入門到精通三部曲之常見緣由與用戶實踐
騰訊Bugly特約做者: 姚潮生java

常見緣由

1.集合類數組

集合類若是僅僅有添加元素的方法,而沒有相應的刪除機制,致使內存被佔用。若是這個集合類是全局性的變量 (好比類中的靜態屬性,全局性的 map 等即有靜態引用或 final 一直指向它),那麼沒有相應的刪除機制,極可能致使集合所佔用的內存只增不減。緩存

2.單例模式markdown

不正確使用單例模式是引發內存泄露的一個常見問題,單例對象在被初始化後將在 JVM 的整個生命週期中存在(以靜態變量的方式),若是單例對象持有外部對象的引用,那麼這個外部對象將不能被 JVM 正常回收,致使內存泄露網絡

3.Android組件或特殊集合對象的使用app

BraodcastReceiver,ContentObserver,FileObserver,Cursor,Callback等在 Activity onDestroy 或者某類生命週期結束以後必定要 unregister 或者 close 掉,不然這個 Activity 類會被 system 強引用,不會被內存回收。異步

不要直接對 Activity 進行直接引用做爲成員變量,若是不得不這麼作,請用 private WeakReference mActivity 來作,相同的,對於Service 等其餘有本身聲明週期的對象來講,直接引用都須要謹慎考慮是否會存在內存泄露的可能。函數

4. Handleroop

要知道,只要 Handler 發送的 Message 還沒有被處理,則該 Message 及發送它的 Handler 對象將被線程 MessageQueue 一直持有。因爲 Handler 屬於 TLS(Thread Local Storage) 變量, 生命週期和 Activity 是不一致的。所以這種實現方式通常很難保證跟 View 或者 Activity 的生命週期保持一致,故很容易致使沒法正確釋放。如上所述,Handler 的使用要尤其當心,不然將很容易致使內存泄露的發生。測試

5.Thread 內存泄露

線程也是形成內存泄露的一個重要的源頭。線程產生內存泄露的主要緣由在於線程生命週期的不可控。好比線程是 Activity 的內部類,則線程對象中保存了 Activity 的一個引用,當線程的 run 函數耗時較長沒有結束時,線程對象是不會被銷燬的,所以它所引用的老的 Activity 也不會被銷燬,所以就出現了內存泄露的問題。

6.一些不良代碼形成的內存壓力

有些代碼並不形成內存泄露,可是它們,或是對沒使用的內存沒進行有效及時的釋放,或是沒有有效的利用已有的對象而是頻繁的申請新內存。

6.1 Bitmap 沒調用 recycle().
Bitmap 對象在不使用時,咱們應該先調用 recycle() 釋放內存,而後才它設置爲 null. 由於加載 Bitmap 對象的內存空間,一部分是 java 的,一部分 C   的(由於 Bitmap 分配的底層是經過 JNI 調用的 )。 而這個 recyle() 就是針對 C 部分的內存釋放。

6.2 構造 Adapter 時,沒有使用緩存的 convertView。


以業務測試過程當中常見的部份內存泄露實例來講明:

1. callback只有add操做,沒有註銷remove

從引用關係能夠看到當前 view 被 callback 引用,而 callback 被外部對象 sharkprotocolQueue 持有引用而致使泄漏。

2. 發送延時消息時,若是該消息未處理,在退出頁面後會致使該頁面沒法回收。

Android 應用啓動的時候會建立 UI 主線程的 Looper 對象,它存在於整個應用的生命週期,用於處理消息隊列裏的 Message。而這些 Message 會引用發送該消息的 Handler 對象。

那麼問題來了,若是這些 Handler 是 Activity 的內部類,那麼當這些 Handler 的消息未處理完或者消息自己是延時消息的話,就會致使 Activity 退出後,從 Activity 到 Handler 到 Message 到 Looper 的引用鏈條一直存在,從而致使 Activity 的泄露!

3. 異步線程未完成前退出 Activity 等組件,可能會致使界面資源沒法釋放。

這種狀況是典型的線程對象致使的內存泄露。緣由也很簡單,線程 Thread 對象的 run 任務未執行完以前,對象自己是不會釋放的。所以 Activity 等組件對象內的線程對象成員若是有耗時任務(通常也都是耗時任務),就會致使一直持有組件自己的引用內存泄露!

本文部份內容和經驗摘自網絡,結合本次內存泄露的排查總結予以概括。


優秀實踐

  1. 對activity等組件的引用應該控制在activity的生命週期以內; 若是不能就考慮使用
    getApplicationContext或者getApplication,以免activity被外部長生命週期的對象引用而泄露

  2. 在代碼複審的時候關注長生命週期對象:全局性的集合、單例模式的使用、類的static變量等等。

  3. 儘可能不要在靜態變量或者靜態內部類中使用非靜態外部成員變量(包括context),即便要使用,也要考慮適時把外部成員變量置空;也能夠在內部類中使用弱引用來引用外部類的變量;

  4. Handler的持有的引用對象最好使用弱引用,資源釋放時也能夠清空Handler裏面的消息。好比在Activity onStop或者onDestroy的時候,取消掉該Handler對象的Message和Runnable:

  5. removeCallbacks(Runnable r)或removeMessages(int what),或removeCallbacksAndMessages(null)等。

  6. 線程Runnable執行耗時操做,注意在頁面返回時及時取消或者把Runnable寫成靜態類。
    a)     若是線程類是內部類,改成靜態內部類。
    b)     線程內若是須要引用外部類對象如context,須要使用弱引用。

  7. 在Java的實現過程當中,也要考慮其對象釋放,最好的方法是在不使用某對象時,顯式地將此對象賦空,如清空對圖片等資源有直接引用或者間接引用的數組(使用array.clear();array = null),最好遵循誰建立誰釋放的原則。


騰訊Bugly簡介

Bugly是騰訊內部產品質量監控平臺的外發版本,其主要功能是App發佈之後,對用戶側發生的Crash以及卡頓現象進行監控並上報,讓開發同窗能夠第一時間瞭解到App的質量狀況,及時機型修改。目前騰訊內部全部的產品,均在使用其進行線上產品的崩潰監控。

相關文章
相關標籤/搜索