1. 程式人生 > >Android記憶體洩漏場景及解決方法

Android記憶體洩漏場景及解決方法

本文包括以下內容:
1. 記憶體洩漏原理
2. Android記憶體洩漏發生的情況
3. 檢測記憶體洩漏的工具、方法
4. 如何避免記憶體洩漏

更多Android面試相關請點選
- 四步準備Android面試
- Android開發概要
- 大疆提前批第一次電面
- 大疆提前批第二次電面
- 大疆提前批終面
- 大疆提前批終面後加面

1. 記憶體洩漏原理

簡單來說,Java的記憶體洩漏就是物件不再使用的時候,無法被JVM回收。記憶體洩漏最終會引發Out Of Memory。

在Java中,判斷物件是否仍在使用的方法是:引用計數法,可達性分析。

引用計數法就是對每個物件所持有的引用進行計數,計數為0,則沒有引用,判斷為可回收狀態。但是此方法存在的問題是迴圈引用,即A持有B的引用,B持有A的引用,同時AB不再使用時,無法回收AB,發生記憶體洩漏。

可達性分析就是從一些GC Root 物件出發,去遍歷所含有物件的引用,以此遞迴。像樹一樣,從根向樹枝查詢可達的物件。最後沒有標記到的物件即為可回收物件,解決了迴圈引用的問題。

但是即使採用可達性分析的方法,還是可能由於程式編寫的問題引發記憶體洩漏。總結來說就是長週期的物件持有了短週期物件的引用,導致短週期物件無法回收,引起記憶體洩漏。

2. Android記憶體洩漏發生的情況

記憶體洩漏是否發生的關鍵在於物件之間生命週期的長短。下面是可能發生記憶體洩漏的情況:

比較典型的是Activity的Context,包含大量的引用,比如View Hierarchies和其他資源。一旦無法釋放Context,也意味著無法釋放它指向的所有物件。

  • 靜態變數:靜態變數的生命週期和應用的生命週期一樣長。如果靜態變數持有某個Activity的context,則會引發對應Activity無法釋放,導致記憶體洩漏。如果持有application的context,就沒有問題(以下例子是指Activity銷燬時沒有釋放的情況)

    • 單例模式:內部實現是靜態變數和方法
    • 靜態的View:view預設持有Activity的context
    • 靜態Activity
  • 監聽器:當使用Activity的context註冊監聽,不再需要監聽時沒有取消註冊。比如感測器的監聽等

  • 內部類

    • 匿名內部類:持有外部類引用。匿名內部類和非同步任務一起出現時,可能發生記憶體洩漏。Activity回收時,非同步任務沒有執行完畢會導致記憶體洩漏的發生。因為匿名任務類持有Activity引用,當匿名任務類的引用被另一執行緒持有,導致生命週期不一致的問題,進而導致記憶體洩漏
    • 匿名的AsyncTask

      new AsyncTask<String, String, String>() {
                  @Override
                  protected String doInBackground(String... params) {
                      // doSomething
                      return null;
                  }
              };
    • 匿名的TimerTask

      new Timer().schedule(new TimerTask() {
                  @Override
                  public void run() {
      
                       // doSomething
                  }
              }, 1000);
    • 匿名的Thread或Runnable

      new Thread() { 
           @Override public void run() { 
               while(true);
               }
       }.start();
    • 非靜態內部類:持有外部類引用

      • Handler:我們知道Handler處理訊息是序列的,所以當Activity已經需要回收,但Looper仍有訊息未處理完畢時會發生記憶體洩漏。因為Looper使用ThreadLocal儲存,ThreadLocal是靜態的,生命週期與當前應用一致。同時Looper持有MessageQueue的引用,MessageQueue持有Handler引用(msg.target),Handler持有外部Activity引用,導致Activity無法回收
      • 非靜態內部類有一個靜態的例項:非靜態內部類持有外部類引用,如果在某個地方有個非靜態內部類的靜態例項的話,同樣會引起記憶體洩漏
  • 資源物件未關閉:BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等資源,使用後未關閉會導致記憶體洩漏。因為資源性物件往往都用了一些緩衝,緩衝不僅存在於 java虛擬機器內,還存在於java虛擬機器外。如果僅僅是把它的引用置null,而不關閉它們,也會造成記憶體洩漏

  • 容器中的物件沒有清理:集合一般佔用記憶體較大,不及時關閉會導致記憶體緊張(不會導致記憶體洩漏,而會導致可用記憶體大大減少)
  • webview

3. 檢測、分析記憶體洩漏的工具

  • MemoryMonitor:隨時間變化,記憶體佔用的變化情況
  • MAT:輸入HRPOF檔案,輸出分析結果
    • Histogram:檢視不同型別物件及其大小
    • DominateTree:物件佔用記憶體及其引用關係
    • MAT使用教程
  • LeakCanary:實時監測記憶體洩漏的庫(LeakCanary原理)

4. 如何避免記憶體洩漏

長週期的物件持有了短週期物件的引用,導致短週期物件無法回收,引起記憶體洩漏。所以在使用某個物件時,我們需要仔細研究物件的生命週期,當處理一些佔用記憶體較大並且生命週期較長的物件時,可以使用軟引用。對於一些資源操作物件,及時關閉。

  • 不要在匿名內部類中進行非同步操作
  • 將非靜態內部類轉為靜態內部類 + WeakReference(弱引用)的方式
  • 在 Activity 回撥 onDestroy 時或者 onStop 時
    • 移除訊息佇列 MessageQueue 中的訊息
    • 靜態變數置null
    • 停止非同步任務
    • 取消註冊
  • 使用Context時,儘量使用Application 的 Context
  • 儘量避免使用static 成員變數。另外可以考慮lazy初始化
  • 為webView開啟另外一個程序,通過AIDL與主執行緒進行通訊,WebView所在的程序可以根據業務的需要選擇合適的時機進行銷燬,從而達到記憶體的完整釋放
  • 及時關閉資源。Bitmap 使用後呼叫recycle()方法

防止記憶體溢位的方法
- 及時清理容器,將集合裡的東西clear,然後置為null
- 使用adapter時,使用ViewHolder來複用convertView
- 優化資料結構
- 比如HashMap和ArrayMap,優先使用ArrayMap;優先使用基本型別,而非包裝類
- 減少佔記憶體較大的列舉的使用
- 採用三級快取機制:LRUCache
- 圖片壓縮:inSampleSize、RGB_565替換RGB_8888

  • 儘量不要在迴圈中建立大量物件

注:

  1. 在C++ 中,記憶體分配釋放有程式設計師自己管理。記憶體洩漏發生的情況是,如果有些物件被分配了記憶體空間,然後卻不可達,由於C++中沒有垃圾回收機制,導致無法再釋放這些記憶體空間。

  2. 對於Java程式設計師來說,GC基本是透明的,不可見的。雖然,我們只有幾個函式可以訪問GC,例如執行GC的函式System.gc(),但是根據Java語言規範定義, 該函式不保證JVM的垃圾收集器一定會執行。因為,不同的JVM實現者可能使用不同的演算法管理GC。通常,GC的執行緒的優先級別較低。JVM呼叫GC的策略也有很多種,有的是記憶體使用到達一定程度時,GC才開始工作,也有定時執行的,有的是平緩執行GC,有的是中斷式執行GC。就是說GC是不可控的,基本是透明的。

  3. Java對引用的分類有 Strong reference, SoftReference, WeakReference, PhatomReference 四種。

    • 強引用(StrongReference):JVM 寧可丟擲 OOM ,也不會讓 GC 回收具有強引用的物件
    • 軟引用(SoftReference):只有在記憶體空間不足時,才會被回的物件
    • 弱引用(WeakReference):在 GC 時,一旦發現了只具有弱引用的物件,不管當前記憶體空間足夠與否,都會回收它的記憶體
    • 虛引用(PhantomReference):任何時候都可以被GC回收,當垃圾回收器準備回收一個物件時,如果發現它還有虛引用,就會在回收物件的記憶體之前,把這個虛引用加入到與之關聯的引用佇列中。程式可以通過判斷引用佇列中是否存在該物件的虛引用,來了解這個物件是否將要被回收。可以用來作為GC回收Object的標誌

如有任何遺漏或錯誤,歡迎反饋

版權宣告
本文首發自簡書:
搜尋作者:QinGeneral
無需授權即可轉載,甚至無需保留以上版權宣告;
轉載時請務必註明作者。