1. 程式人生 > >android記憶體洩露深入研究

android記憶體洩露深入研究

首先抄上百科

隱式記憶體洩漏:程式在執行過程中不停的分配記憶體,但是直到結束的時候才釋放記憶體。嚴格的說這裡並沒有發生記憶體洩漏,因為最終程式釋放了所有申請的記憶體。但是對於一個伺服器程式,需要執行幾天,幾周甚至幾個月,不及時釋放記憶體也可能導致最終耗盡系統的所有記憶體。所以,我們稱這類記憶體洩漏為隱式記憶體洩漏。從使用者使用程式的角度來看,記憶體洩漏本身不會產生什麼危害,作為一般的使用者,根本感覺不到記憶體洩漏的存在。真正有危害的是記憶體洩漏的堆積,這會最終耗盡系統所有的記憶體。從這個角度來說,一次性記憶體洩漏並沒有什麼危害,因為它不會堆積,而隱式記憶體洩漏危害性則非常大,因為較之於常發性和偶發性記憶體洩漏它更難被檢測到。

Android應用層開發絕大部分能產生的記憶體洩露都是隱式記憶體洩露,因為應用結束後記憶體都能釋放的。

我們這裡以比較主流的LeakCanary檢測android記憶體洩露的方法來分析它對記憶體洩露的判定方法。

LeakCanary認為物件的生命週期結束時還沒釋放物件就認為是記憶體洩露,而activity的生命週期結束就是呼叫onDestroy的時候了。但LeakCanary會給你gc的時間,這個時間預設是5S。也就是說,當activity說自己的生命週期已經到頭了,我LeakCanary再給你5S時間處理後事,如果沒處理完,我就認為你是記憶體洩露了。

private void test() {

    new AsyncTask<Void, Void, Void>() {

      …………………………………..
    }

}

看上面程式碼Android Studio中報的警告

This AsyncTask class should be static or leaks might occur (anonymous android.os.AsyncTask) less... (Ctrl+F1)

A static field will leak contexts.  Non-static inner classes have an implicit reference to their outer class. If that outer class is for example a Fragment or Activity, then this reference means that the long-running handler/loader/task will hold a reference to the activity which prevents it from getting garbage collected.  Similarly, direct field references to activities and fragments from these longer running instances can cause leaks.  ViewModel classes should never point to Views or non-application Contexts.

其實就是內部類會持有外部類的引用,當Activity要結束時,AsyncTask 正在執行(沒有cancel,或doInBackground正在執行),此時就認為有可能洩露了。而LeakCanary判定doInBackground的執行時間超過5s就認為屬於記憶體洩露。

那麼我們用LeakCanary再來驗證下以下用法是否會造成記憶體洩漏

public void test() {

    new Thread() {

        @Override

        public void run() {

            super.run();

            try {

                sleep(10000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

    }.start();

}

呼叫這個方法後按返回結束介面,LeakCanary果然直接報記憶體洩漏。

好吧,這就尷尬了啊。執行緒裡面本來就是用來處理耗時任務,現在你告訴我這樣都算記憶體洩露,那能怎麼辦呢?只有用static處理麼,static常駐記憶體就不說了,靜態類不能訪問外部類的非靜態成員,我一個個變數的傳過去還不是得增加引用,這些引用還不是被佔著。好吧,這裡又提供了一個弱引用,弱引用需要在判斷物件沒被回收的時候才繼續進行操作。好吧,終於有了個終極解決方案。

問題解決了,現在我們分析下解決這個問題付出的代價。

  1. 額外的常駐記憶體的物件。
  2. 額外的臨時記憶體物件的建立以及弱引用的get()非空判斷。

結論:事實上Google原生應用中有很多Handler,執行緒,或非同步的使用中都沒有進行這種隱式記憶體洩漏的規避。因為實際上執行裡面包含的程式碼片段的最多執行時間也就那麼幾秒或幾百毫秒,絕大部分情況下即便出現了此類記憶體洩露的場景,在極短的時間內也能回收這些記憶體。對使用者的使用並不會造成影響。而且你能確定當用戶關閉當前介面時,使用者就不希望他之前進行的耗時事件仍然繼續完成麼。其實很多時候程式的健壯性與極致的流暢性是站在對立面,此時又該如何取捨?就像javaC++誰更好這個問題一樣。歸根結底只有實用的,能讓使用者獲得最好體驗的才是正道。