Android Studio +MAT 分析內存泄漏實戰

對於內存泄漏,在Android中若是不注意的話,仍是很容易出現的,尤爲是在Activity中,比較容易出現,下面我就說下本身是如何查找內存泄露的。html

首先什麼是內存泄漏?

內存泄漏就是一些已經不使用的對象還存在於內存之中且垃圾回收機制沒法回收它們,致使它們常駐內存,會使內存消耗愈來愈大,最終致使程序性能變差。
其中在Android虛擬機中採用的是根節點搜索算法枚舉根節點判斷是不是垃圾,虛擬機會從GC Roots開始遍歷,若是一個節點找不到一條到達GC Roots的路線,也就是沒和GC Roots 相連,那麼就證實該引用無效,能夠被回收,內存泄漏就是存在一些很差的調用致使一些無用對象和GC Roots相連,沒法被回收。java

既然知道了什麼是內存泄漏,天然就知道如何去避免了,就是咱們在寫代碼的時候儘可能注意產生對無用對象長時間的引用,提及來簡單,可是須要足夠的經驗才能達到,因此內存泄漏仍是比較容易出現的,既然不容易徹底避免,那麼咱們就要能發現程序中出現的內存泄漏並修復它,
下面我就說說如何發現內存泄漏的吧。android

查找內存泄漏:

好比說下面這個代碼:算法

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        String string = new String();

    }

    public void click(View view){
        Intent intent = new Intent();
        intent.setClass(getApplicationContext(),SecondActivity.class);
        startActivity(intent);
    }
}
public class SecondActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(8000000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        new Thread(runnable).start();
    }
}

每次跳轉到這個Activity中時都會調用一個線程,而後這個線程會執行runnable的run方法 因爲Runnable是一個匿名內部對象 因此握有SecondActivity的引用,所以
很簡單的兩個Activity,可由MainActivity跳轉到SecondActivity中,
下面咱們從MainActivity跳到SecondActivity 而後從SecondActivity返回MainActivity,連續這樣5次 ,最終返回MainActivity,按照常理來講,咱們從SecondActivity返回MainActivity以後 SecondActivity就該被銷燬回收,可實際可能並非這樣。數組

這時候要判斷髮沒發生內存溢出就要使用工具了!下面有兩種方式app

1.利用MAT工具查找eclipse

首先打開AS中的Android Device Monitor工具 具體位置以下圖:
AS Android Device Monitor位置
打開後會出現以下的界面
ADM界面
先選中你要檢測的應用的包名,而後點擊下圖畫圈的地方,會在程序包名後標記一個圖標

接下來要作的就是操做咱們的app 來回跳轉5次。
以後點擊下圖的圖標 就可導出hprof文件進行分析了
ide

導出文件以下圖所示:
hprof文件
獲得了hprof文件 咱們就能夠利用MAT工具進行分析了,
打開MAT工具
若是沒有 能夠在下面網址下載
MAT工具下載地址
工具

界面以下圖所示:
oop

打開咱們先前導出的hprof文件 ,不出意外會報下面的錯誤

這是由於MAT是用來分析java程序的hprof文件的 與Android導出的hprof有必定的格式區別,所以咱們須要把導出的hprof文件轉換一下,sdk中提供給咱們轉換的工具 hprof-conv.exe 在下圖的位置
hprof-conv位置
接下來咱們cd到這個路徑下執行這個命令轉換咱們的hprof文件便可,以下圖
轉換hprof文件
其中 hprof-conv 命令 這樣使用
hprof-conv 源文件 輸出文件
好比 hprof-conv E:\aaa.hprof E:\output.hprof
就是 把aaa.hprof 轉換爲output.hprof輸出 output.hprof就是咱們轉換以後的文件,圖中 mat2.hprof就是咱們轉換完的文件。

接下來 咱們用MAT工具打開轉換以後的mat2.hprof文件便可 ,打開後不報錯 以下圖所示:
MAT打開hprof文件
以後咱們就能夠查看當前內存中存在的對象了,因爲咱們內存泄漏通常發生在Activity中,所以只須要查找Activity便可。
點擊下圖中標記的QQL圖標 輸入 select * from instanceof android.app.Activity
相似於 SQL語句 查找 Activity相關的信息 點擊 紅色歎號執行後 以下圖所示:
QQL

接下來 咱們就能夠看到下面過濾到的Activity信息了
如上圖所示, 其中內存中還存在 6個SecondActivity實例,可是咱們是想要所有退出的,這代表出現了內存泄漏

其中 有 Shallow size 和 Retained Size兩個屬性

Shallow Size
對象自身佔用的內存大小,不包括它引用的對象。針對非數組類型的對象,它的大小就是對象與它全部的成員變量大小的總和。
固然這裏面還會包括一些java語言特性的數據存儲單元。針對數組類型的對象,它的大小是數組元素對象的大小總和。
Retained Size
Retained Size=當前對象大小+當前對象可直接或間接引用到的對象的大小總和。(間接引用的含義:A->B->C, C就是間接引用)
不過,釋放的時候還要排除被GC Roots直接或間接引用的對象。他們暫時不會被被當作Garbage。

接下來 右擊一個SecondActivity

選擇 with all references
打開以下圖所示的頁面

查看下圖的頁面
看到 this0引用了這個Activitythis0是表示 內部類的意思,也就是一個內部類引用了Activity 而 this$0又被 target引用 target是一個線程,緣由找到了,內存泄漏的緣由 就是 Activity被 內部類引用 而內部類又被線程使用 所以沒法釋放,咱們轉到這個類的代碼處查看

public class SecondActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(8000000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        new Thread(runnable).start();
    }
}

確實 在 SecondActivity中 存在Runnable 內部類對象,而後又被線程 使用,而線程要執行8000秒 所以 SecondActivity對象被引用 沒法釋放,致使了內存溢出。
要解決這種的內存溢出,要及時在Activity退出時結束線程(不過不大好結束。。),或者良好的控制線程執行的時間便可。

這樣咱們就找出了這個程序中的內存溢出。

2.直接利用Android Studio的 Monitor Memory 查找內存溢出
仍是利用上面那個程序,我就簡單點說了。

首先 在手機上運行程序,打開AS的 Minotor 界面 查看Memory 圖像

點擊 小卡車圖標(圖中1位置圖標) 能夠觸發一次 GC

點擊 圖中2位置圖標能夠查看hprof文件

左邊是 內存中的對象,在裏面找 Activity 看存不存在咱們但願已經回收的Activity 若是 出現咱們指望已經回收的Activity,單擊 就會在右邊顯示它的總的個數,點擊右邊的某個,能夠顯示 它的GC Roots的樹關係圖 ,查看關係圖就能夠找出發生內存泄漏的位置(相似於第一種方式)

這樣就完成了內存泄漏的查找。

其中內存泄漏產生的緣由在Android中大體分爲如下幾種:

1.static變量引發的內存泄漏
由於static變量的生命週期是在類加載時開始 類卸載時結束,也就是說static變量是在程序進程死亡時才釋放,若是在static變量中 引用了Activity 那麼 這個Activity因爲被引用,便會隨static變量的生命週期同樣,一直沒法被釋放,形成內存泄漏。

解決辦法:
在Activity被靜態變量引用時,使用 getApplicationContext 由於Application生命週期從程序開始到結束,和static變量的同樣。

2.線程形成的內存泄漏
相似於上述例子中的狀況,線程執行時間很長,及時Activity跳出還會執行,由於線程或者Runnable是Acticvity內部類,所以握有Activity的實例(由於建立內部類必須依靠外部類),所以形成Activity沒法釋放。
AsyncTask 有線程池,問題更嚴重

解決辦法:
1.合理安排線程執行的時間,控制線程在Activity結束前結束。
2.將內部類改成靜態內部類,並使用弱引用WeakReference來保存Activity實例 由於弱引用 只要GC發現了 就會回收它 ,所以可儘快回收

3.BitMap佔用過多內存
bitmap的解析須要佔用內存,可是內存只提供8M的空間給BitMap,若是圖片過多,而且沒有及時 recycle bitmap 那麼就會形成內存溢出。

解決辦法:
及時recycle 壓縮圖片以後加載圖片

4.資源未被及時關閉形成的內存泄漏
好比一些Cursor 沒有及時close 會保存有Activity的引用,致使內存泄漏

解決辦法:
在onDestory方法中及時 close便可

5.Handler的使用形成的內存泄漏
因爲在Handler的使用中,handler會發送message對象到 MessageQueue中 而後 Looper會輪詢MessageQueue 而後取出Message執行,可是若是一個Message長時間沒被取出執行,那麼因爲 Message中有 Handler的引用,而 Handler 通常來講也是內部類對象,Message引用 Handler ,Handler引用 Activity 這樣 使得 Activity沒法回收。

解決辦法:
依舊使用 靜態內部類+弱引用的方式 可解決

其中還有一些關於 集合對象沒移除,註冊的對象沒反註冊,代碼壓力的問題也可能產生內存泄漏,可是使用上述的幾種解決辦法通常都是能夠解決的。

參考博客:https://www.cnblogs.com/taoweiji/p/5760537.html

相關文章
相關標籤/搜索