三、Android效能優化之常見的記憶體洩漏分析

kpioneer123發表於2018-01-19

記憶體洩漏分析: 往往做專案的時候情況非常複雜,或者專案做得差不多了想起來要效能優化檢查下記憶體洩露。

如何找到專案中存在的記憶體洩露的這些地方呢?

1.確定是否存在記憶體洩露 1)Android Monitors的記憶體分析 最直觀的看記憶體增長情況,知道該動作是否發生記憶體洩露。 動作發生之前:GC完後記憶體1.4M; 動作發生之後:GC完後記憶體1.6M

2)使用MAT記憶體分析工具 MAT分析heap的總記憶體佔用大小來初步判斷是否存在洩露 Heap檢視中有一個Type叫做data object,即資料物件,也就是我們的程式中大量存在的類型別的物件。 在data object一行中有一列是“Total Size”,其值就是當前程式中所有Java資料物件的記憶體總量, 一般情況下,這個值的大小決定了是否會有記憶體洩漏。 我們反覆執行某一個操作並同時執行GC排除可以回收掉的記憶體,注意觀察data object的Total Size值, 正常情況下Total Size值都會穩定在一個有限的範圍內,也就是說由於程式中的的程式碼良好,沒有造成物件不被垃圾回收的情況。 反之如果程式碼中存在沒有釋放物件引用的情況,隨著操作次數的增多Total Size的值會越來越大。 那麼這裡就已經初步判斷這個操作導致了記憶體洩露的情況。

2.先找懷疑物件(哪些物件屬於洩露的) MAT對比操作前後的hprof來定位記憶體洩露是洩露了什麼資料物件。(這樣做可以排除一些物件,不用後面去檢視所有被引用的物件是否是嫌疑) 快速定位到操作前後所持有的物件哪些是增加了(GC後還是比之前多出來的物件就可能是洩露物件嫌疑犯) 技巧:Histogram中還可以對物件進行Group,比如選擇Group By Package更方便檢視自己Package中的物件資訊。

  1. MAT分析hprof來定位記憶體洩露的原因所在。(哪個物件持有了上面懷疑出來的發生洩露的物件) 1)Dump出記憶體洩露“當時”的記憶體映象hprof,分析懷疑洩露的類; 2)把上面2得出的這些嫌疑犯一個一個排查個遍。步驟: (1)進入Histogram,過濾出某一個嫌疑物件類 (2)然後分析持有此類物件引用的外部物件(在該類上面點選右鍵List Objects--->with incoming references) (3)再過濾掉一些弱引用、軟引用、虛引用,因為它們遲早可以被GC幹掉不屬於記憶體洩露 (在類上面點選右鍵Merge Shortest Paths to GC Roots--->exclude all phantom/weak/soft etc.references) (4)逐個分析每個物件的GC路徑是否正常 此時就要進入程式碼分析此時這個物件的引用持有是否合理,這就要考經驗和體力了! (比如上課的例子中:旋轉螢幕後MainActivity有兩個,肯定MainActivity發生洩露了, 那誰導致他洩露的呢?原來是我們的CommonUtils類持有了旋轉之前的那個MainActivity他, 那是否合理?結合邏輯判斷當然不合理,由此找到記憶體洩露根源是CommonUtils類持有了該MainActivity例項造成的。 怎麼解決?罪魁禍首找到了,怎麼解決應該不難了,不同情況解決辦法不一樣,要靠你的智慧了。)

判斷一個應用裡面記憶體洩露避免得很好,怎麼看? 當app退出的時候,這個程式裡面所有的物件應該就都被回收了,尤其是很容易被洩露的(View,Activity)是否還記憶體當中。 可以讓app退出以後,檢視系統該程式裡面的所有的View、Activity物件是否為0. 工具:使用AndroidStudio--AndroidMonitor--System Information--Memory Usage檢視Objects裡面的views和Activity的數量是否為0.

System Information

System Information 選項

Memory Usage

發現任然有兩個activity沒有關閉

==================記憶體洩漏經常出現的例子===================== 記憶體洩漏(Memory Leak): 程式中某些物件已經沒有使用價值,但是他們卻還可以直接或間接地被引用到GC Root,導致無法回收。 當記憶體洩漏過多的時候,再加上應用本身佔用的記憶體,(日積月累)最終就會導致記憶體溢位OOM。 現象:當我們使用微信很長時間,也出現了程式崩潰現象。(這就是記憶體洩漏日積月累導致OOM) 記憶體溢位(OOM): 當應用佔用的heap資源超過了Dalvik虛擬機器分配的記憶體就會記憶體溢位。比如:載入大量圖片。

#####1.靜態變數引起的記憶體洩漏 當呼叫getInstance時,如果傳入的context時Activity的context。只要這個單例沒有被釋放,那麼Activity也不會被釋放一直到程式退出才會釋放。

public class CommonUtils {

    private static CommonUtils instance;

    private Context context;

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

    public static CommonUtils getInstance(Context context) {

        if (instance == null) {
            instance = new CommonUtils(context);
        }
        return instance;
    }
}
複製程式碼

#####2.非靜態內部類引起記憶體洩漏 (包括靜態內部類) 錯誤的示範:

	    public void loadData(){//隱士持有MainActivity例項。MainActivity.this.a
		new Thread(new Runnable() {
		    @Override
		    public void run() {
			while(true){
			    try {
				//int b=a;
				Thread.sleep(1000);
			    } catch (InterruptedException e) {
				e.printStackTrace();
			    }
			}
		    }
		}).start();
	    }
複製程式碼

######解決方案: ######將非靜態內部類修改為靜態內部類。

   public static  void loadData(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
複製程式碼

#####在比如用弱引用解決Handler記憶體洩漏問題

public class MainActivity extends AppCompatActivity {
    int a;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

    }
    private static class MyHandler extends Handler {

        /**
         * 1.直接持有一個外部類的強引用,會記憶體洩漏
         */
//        private MainActivity mainActivity;
//        public MyHandler(MainActivity mainActivity) {
//            this.mainActivity = mainActivity;
//        }


        /**
         * 2.使用弱應用
         * WeakReference 是一個弱引用,當所引用的物件在JVM內不再強引用時,GC後 weak reference 將會被自動回
         */
        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:
                    //載入外部資料
                    int b = main.a;
                    break;
            }
        }
    }
}
複製程式碼

WeakReference --- 弱引用 WeakReference 是一個弱引用, 當所引用的物件在 JVM 內不再有強引用時, GC 後 weak reference 將會被自動回 回收時機:在垃圾回收的時候;使用:同軟引用; 生命週期:GC後終止

當時用軟應用或者弱應用的時候,MainActivity難道很容易或者可以被GC回收嗎? GC回收的機制是什麼?當MainActivity不被任何的物件引用。 雖然Handler裡面用的是軟引用/弱引用,但是並不意味著不存在其他的物件引用該MainActivity 當MainActivity都被回收了,那他裡面的Handler自然沒必要在進行處理,return 即可。

Android 非靜態內部類/匿名類引起的記憶體洩漏 android non-static內部類導致的記憶體洩露

#####3.不需要用的監聽未移除會發生記憶體洩漏

例1

        final TextView tv_test = (TextView) findViewById(R.id.tv_test);
        tv_test.getViewTreeObserver().addOnWindowFocusChangeListener(new ViewTreeObserver.OnWindowFocusChangeListener() {
            @Override
            public void onWindowFocusChanged(boolean hasFocus) {
                //監聽view的載入,view 載入出來的時候,計算它的寬高
                // 計算完後,一定要移除這個監聽
                tv_test.getViewTreeObserver().removeOnWindowFocusChangeListener(this);
            }
        });
複製程式碼

例2

        SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
        Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
        SensorEventListener listener = (SensorEventListener) this;
        sensorManager.registerListener(listener,sensor,SensorManager.SENSOR_DELAY_FASTEST);
        sensorManager.unregisterListener(listener);
複製程式碼

#####4.資源未關閉引起的記憶體洩漏情況 對於使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap,自定義屬性attribute等資源的程式碼,應該在Activity銷燬時及時關閉或者登出,否則這些資源將不會被回收,造成記憶體洩漏。

#####5.無限迴圈動畫 沒有在onDestory中停止動畫,否則Activity就會變成洩漏物件。 比如,輪播圖效果

特別感謝: 動腦學院Ricky

相關文章