Android內存泄露分析以及工具的使用

Android性能優化對於開發者來講是一個不可小覷的問題,若是軟件的性能極差,形成界面卡頓,甚至直接掛掉,對於用戶來講是一個極其致命的,可能會致使用戶直接把應用給卸載了。相反的,若是把性能優化得極致,運行得很流暢,從而增長用戶的好感,獲得好評,因此性能優化對於開發者來講是很是重要的。java

Android的性能優化一般涉及到內存泄露檢測、渲染性能優化、電量優化、網絡優化和Bitmap內存管理優化,以及多線程優化等等,固然性能優化的不止這些,除此以外還有安裝包優化和數據傳輸效率等,因此Android的性能優化涉及的範圍是比較廣的。心急吃不了熱豆腐,所以須要咱們一點點來學習,慢慢研究。性能優化

內存泄露

內存泄露,關乎到開發者自己寫代碼的問題,因此平時開發者寫代碼要有嚴謹性和清晰的邏輯,申請的內存,沒用以後,就要釋放掉。那麼什麼是內存泄露呢?bash

瞭解內存泄露,首先須要瞭解java的內存分配,其中主要包括靜態存儲區、棧區、堆區、寄存器、常量池等。網絡

靜態存儲區:內存在程序編譯的時候就已經分配好,這塊的內存在程序整個運行期間都一直存在,主要存放靜態數據、全局的static數據和一些常量。多線程

棧區:保存局部變量的值,其中包括:用來保存基本數據類型的值、保存類的實例(即堆區對象的引用(指針)),以及用來保存加載方法時的幀。也就是說函數一些內部變量的存儲在棧區,函數執行結束的時,這些存儲單元就會自動被釋放掉。由於棧內存內置在處理器的裏面,因此運算速度很快,可是棧區的容量有限。併發

堆區:也叫作動態內存分配,用來存放動態產生的數據,如new出來的對象。用malloc或者new來申請分配一個內存。在C/C++可能須要本身負責釋放,但在java裏面直接依賴GC機制。eclipse

寄存器:JVM內部虛擬寄存器,存取速度很是快,程序不可控制。ide

常量池:存放常量。函數

關於內存分配,讀者能夠參考《Java 內存分配全面淺析》工具

棧和堆的區別: 1)堆是不連續的內存區域,堆空間比較靈活也特別大。 2)棧式一塊連續的內存區域,大小是由操做系統決定的。

因爲堆是不連續的內存區域,管理起來特別的麻煩,若是頻繁的new和remove,可能會形成大量的內存碎片,所形成的內存碎片就形成了內存泄露,這樣就會致使運行效率低下。可是對於棧,棧的特色是先進後出,進出徹底不會產生碎片,運行效率高且穩定。所以咱們關於內存泄露,主要看堆內存。

內存泄露(memory leak):是指程序在申請內存後,沒法釋放已申請的內存空間。也就是說當一個對象已經再也不使用了,本該被回收時,但有另一個正在使用的對象持有它的引用從而就致使對象不能被回收。這種致使了本該被回收的對象不能被回收而停留在堆內存中,就產生了內存泄漏。

內存溢出

內存溢出(out of memory):是指程序在申請內存時,沒有足夠的內存空間供其使用,出現out of memory。在Android中若是出現內存溢出,也就是咱們常常看到的OOM狀況,那麼應用就會閃退。因爲內存泄露,而致使可用的內存空間愈來愈少,從而致使OOM。所以平時寫代碼特別須要主要內存泄露的狀況,由於一旦出現內存泄露,隨着泄露的內存愈來愈多,就會形成內存溢出。

既然是內存泄露會致使內存溢出,歸根結底仍是須要優化內存泄露。優化內存泄露,首先須要找到內存泄露的地方,而後才能去優化。

肯定內存泄露

1)使用AndroidStudio自帶的Memory Monitors進行內存分析。

monitor

經過可視化能夠觀察到該應用的Memery、CPU、NetWork和GPU變化等狀況。這裏咱們主要觀察Memery(內存)便可,上圖是我打開應用的首頁時Memery的狀況。

而後點擊幾回InitiateGC:

monitor

這是點擊GC以後,穩定下來的狀況,基本上時一條水平線的狀態的了。

monitor

Free:表示還可用的內存,在圖中淺灰色表示。 Allocated:表示已經分配的內存大小,一樣在圖中藍色表示

當我進入下個頁面的時候,明顯看到內存變化

monitor

當我返回上一個頁面的時候,而後GC

monitor

上圖就是返回以後,點擊GC的狀況,Free和Allocated並無變化,說明剛剛進入的那個頁面就沒有出現內存泄露的狀況,若是出現變化比較明顯,那就能夠判斷剛剛所進入的頁面出現了內存泄露的狀況。一樣也可使用Heap Viewer觀察內存泄露。

Heap Viewer

Heap Viewer:可以實時查看App分配的內存大小和空閒內存大小,並發現內存泄露。除此功能之外,Heap Viewer還能夠檢測內存抖動,由於內存抖動的時候,會頻繁發生GC,這個時候咱們只須要開啓Heap Viewer,觀察數據的變化,若是發生內存抖動,會觀察到數據在短期內頻繁更新。

啓動Heap Viewer:

monitor

選擇設備下對應的包名,而後update Heap

monitor

選擇Head,而後GC

monitor

monitor

點擊Cause GC,發現全部的數據都更新了,更新後的表格顯示,在Heap上哪些數據是可用的,選中其中任一行數據,就能夠看到詳細數據。

data object的total size就是當前進程中Java的對象所佔用的內存總量。咱們反覆執行某一個操做並同時執行GC排除能夠回收掉的內存,注意觀察data object的Total Size值,正常狀況下Total Size值都會穩定在一個有限的範圍內,也就是說因爲程序中的的代碼良好,沒有形成對象不被垃圾回收的狀況。反之若是代碼中存在沒有釋放對象引用的狀況,隨着操做次數的增多Total Size的值會愈來愈大。

點擊class object,屏幕上立刻出現大量更新的數據,矩形圖列出這一數據內存分配的數量,以及確切的容量,heap viewer能夠有效地分析程序在堆中所分配的數據類型,以及數量和大小。

Allocation Tracker

除了Head Viewer和Memory Monitor,還可使用Allocation Tracker(分配追蹤器)。

monitor

關於Allocation Tracker能夠查看 《Android性能專項測試之Allocation Tracker(Android Studio)》 這篇文章進行學習。

以上的工具都具備不一樣的特色,具體使用那一個工具能夠按照如下來劃分: 1)Memory Monitor:得到內存的動態視圖 2)Heap Viewer:顯示堆內存中存儲了什麼 3)Allocation Tracker:具體是哪些代碼使用了內存

使用MAT內存分析工具

MAT:Memory Analyzer Tools,一款詳細分析Java堆內存的工具,從而可以分析出內存泄露的詳細狀況。

使用AndroidStudio生成hprof文件:

monitor

生成的hprof文件不能直接交給MAT, MAT是不識別的, 咱們須要右鍵點擊這個文件,轉換成MAT識別的。

monitor

在eclipse中安裝MAT,而後打開hprof文件:

monitor

monitor

使用MAT來分析內存泄露的狀況: 一、根據data object的Total Size,找到內存泄露的操做; 二、找到內存泄露的對象(懷疑對象),也就是經過MAT對比操做先後的hprof文件來定位內存泄露,是那個數據對象內存泄露了; 三、找到內存泄露的緣由,也就是那個對象持有了第2個步驟找出來的發生內存泄露的對象。

具體步驟: 1)進入Histogram,過濾出某一個嫌疑對象類;

monitor

2)分析持有此類對象引用的外部對象;

monitor

3)過濾掉一些弱引用、軟引用、虛引用,由於它們早晚能夠被GC幹掉不屬於內存泄露。

monitor

過濾掉以後就須要進入代碼分析此時的對象的引用持有是否合理,而後進行解決。

內存優化通常分爲兩方面,一方面在開發過程當中避免寫出有內存泄露的代碼,另外一方面就是咱們前面介紹的,利用一些內存分析工具檢測出潛在的內存泄露。我看看平時咱們開發中,那些須要注意內存泄露的地方。

靜態變量引發的內存泄露
private static Context sContext;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_welcome);
        sContext = this;
    }
複製代碼

以上代碼Activity沒法正常銷燬,由於sContext引用了它。

單例模式所形成的內存泄露
public class MyInstance {

    private static MyInstance instance;
    private Context context;

    private MyInstance(Context context) {
        this.context = context;
    }

    public static MyInstance getInstance(Context mcontext) {
        if (instance == null) {
            instance = new MyInstance(mcontext);
        }
        return instance;
    }

    public void setContext(Context context) {
        this.context = context;
    }
}
複製代碼

單例模式的生命週期和Application保持一致,若是在Activity中調用getInstance,把Activity的Context傳入,那麼Activity的對象就會被單例模式的MyInstance所持有,形成內存泄露,其實也是同屬於靜態變量引發的內存泄露,由於instance就是靜態變量。而靜態變量屬於靜態存儲方式,其存儲空間爲內存中的靜態數據區(在靜態存儲區內分配存儲單元),該區域中的數據在整個程序的運行期間一直佔用這些存儲空間(在程序整個運行期間都不釋放)。

非靜態內部類引發內存泄露
//隱式持有Activity實例,Activity.this.a
    public void loadData(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                    try {
                        int b=a;
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
複製代碼

還有Handler使用非靜態內部類的形式:

private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };
複製代碼

如何解決非靜態內部類引發內存泄露的問題呢?這就須要將靜態內部類修改成靜態內部類,由於靜態內部類不會隱式持有外部類。

//解決方案:
    private static class MyHandler extends Handler{
        //設置軟引用保存,當內存一發生GC的時候就會回收。
        private WeakReference<MainActivity> mainActivity;

        public MyHandler(MainActivity mainActivity) {
            this.mainActivity = new WeakReference<MainActivity>(mainActivity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            MainActivity main =  mainActivity.get();
            if(main==null||main.isFinishing()){
                return;
            }
            switch (msg.what){
                case 0:
                    //加載數據
                    // 引用MainActivity.this.a;
                    int b = main.a;
                    break;

            }
        }
    };
複製代碼

在上面的代碼中使用靜態內部類的形式建立了一個繼承Handler的MyHandler,而且內部使用弱引用WeakReference,既WeakReference,若是不用弱引用的話,mainActivity就會直接持有了一個外部類的強引用,致使內存泄露。最好在onDestroy調用Handler的removeCallbacksAndMessages方法。

@Override
    protected void onDestroy() {
        super.onDestroy();
        MyHandler.removeCallbacksAndMessages(null);
    }
複製代碼

上面提到了弱引用,這裏咱們須要瞭解引用相關的知識:

1)StrongReference(強引用):從不回收,在JVM中止的時候纔會終止。

2)SoftReference(軟引用):當內存不足的時候就會回收。

3)WeakReference(弱引用):在垃圾回收的時候就會回收,它在GC後終止。

4)PhatomReference(虛引用):在垃圾回收的時候就會回收,一樣在GC後終止。

資源未關閉引發的內存泄露狀況

平時用到的資源,用完以後須要關閉,防止內存泄露,如BroadCastReceiver、Cursor、Bitmap、IO流和自定義屬性AttributeSet等資源。在自定義的AttributeSet資源用完以後,須要調用attrs.recycle()進行回收。不然會形成內存泄露。

屬性動畫致使的內存泄露

屬性動畫有一類無限循環動畫,若是沒有在onDestroy方法中中止動畫,Activity就會致使內存泄露。由於,若是沒有停掉動畫的話,Activity的View就會被動畫持有,而View又持有了Activity,最終Activity沒法釋放。

用完後的監聽未移除致使內存泄露
public class ListenerCollector {

    static private WeakHashMap<View, MyView.MyListener> sListener = new WeakHashMap<>();

    public void setsListener(View view, MyView.MyListener listener) {
        sListener.put(view, listener);
    }

    public static void clearListeners() {
        //移除全部監聽。
        sListener.clear();
    }

}
複製代碼

如上面的代碼,添加了監聽,使用完以後,須要在onDestroy方法裏須要調用clearListeners方法,移除監聽。關於WeakHashMap的特色就是當除了自身有對key的引用外,若是此key沒有其餘引用那麼此map會自動丟棄此值,如上面的view=null,那麼sListener裏面的view就會被丟棄。

相關文章
相關標籤/搜索