1. 程式人生 > >MAT分析android記憶體洩漏

MAT分析android記憶體洩漏

轉載請標明出處:https://www.cnblogs.com/tangZH/p/10955429.html

 

洩漏,洩漏,漏~

記憶體洩漏怎麼破,什麼是記憶體洩漏?與記憶體溢位有什麼區別?

 

記憶體洩漏(Memory Leak):是指程式中己動態分配的堆記憶體由於某種原因程式未釋放或無法釋放,造成系統記憶體的浪費,導致程式執行速度減慢甚至系統崩潰等嚴重後果。

記憶體溢位(out of memory):是指程式在申請記憶體時,沒有足夠的記憶體空間供其使用,出現out of memory;

 

記憶體洩漏不一定會引起奔潰,但是記憶體溢位一定會

 

Java裡頭有GC垃圾回收機制,他是怎麼判斷該不該回收呢?

Q1:某物件沒有任何引用的時候才進行回收?

A:不。無法往上追溯到GCroot引用點的才回收。

 

Q2:某物件被別的物件引用就不能進行回收?

A:不。軟引用,弱引用,虛引用都可以

 


 

哪些可以作為GCroot引用點:

  • Javastack中引用的物件
  • 方法區中靜態引用指向的物件
  • 方法區中常量引用指向的物件
  • Native方法中JNI引用指向的物件
  • Thread-活著的執行緒

 

adb命令驗證是否存在記憶體洩漏:

1、開啟要測試的apk,然後返回退出到主介面

2、AS的Terminal中輸入命令:

     adb shell dumpsys meminfo com.status.mattest -d 

     com.status.mattest為包名

 

然後便可以看到記憶體的一些情況

拉到下面可以看到:

我們退出APK之後,物件本應該都被回收,然而這裡可以看到,還有view以及Activity佔用著記憶體,由此可以知道記憶體洩漏了。

 


我們點選AS上的按鈕,進行分析。

 執行apk後會出現該介面:

 

 

我們點選MEMORY進入記憶體分析介面:

 

這時候我們需要進行剛剛的操作,開啟apk,然後返回鍵退出,然後點選上圖中垃圾桶形狀的圖示進行垃圾回收,多點幾次,直到記憶體沒有什麼變化了。

然後點選上圖中類似於下載按鈕的圖示獲取記憶體快照。

 

獲取完之後,左邊會出現下面這圖,Head Dump便是獲取記憶體快照後出現的,我們可以點選紅圈中的圖形進行儲存

 

 跟著出現的還有這個視窗。

我們可以通過包來分類檢視自己的專案.

Shallow Heap(淺堆) 表示該物件自身佔用的堆記憶體,不包括它引用的物件。
針對非陣列型別的物件,它的大小就是物件與它所有的成員變數大小的總和。

Retained Heap(深堆) 表示當前物件大小+當前物件可直接或間接引用到的物件的大小總和。
換句話說,Retained Size就是當前物件被GC後,從Heap上總共能釋放掉的記憶體。


不過,釋放的時候還要排除被GC Roots直接或間接引用的物件。他們暫時不會被被當做Garbage。

從圖中可以看出,出現了記憶體洩漏的是CustomUtils,MainActivity,MainActivity$1代表MaiActivity裡面的一個方法。

 

點選MainActivity,可以看到這麼對東西,眼花繚亂,我們根本不知道是哪個引起了記憶體洩漏。那咋辦。鋪墊結束,mat該上場了。

 

MAT

下載mat https://www.eclipse.org/mat/downloads.php

安裝步驟很簡單。

 

開啟MAT後,點選File -> Open Heap Dump 開啟剛剛儲存的記憶體快照,會發現打不開。

因為我們儲存的記憶體快照是不能直接在MAT上開啟的,需要進行轉化。

 

在AS的Terminal視窗輸入:hprof-conv -z A.hprof B.hprof

A.hprof為剛剛儲存後的快照檔案,B.hprof為轉換後的檔案,也就是我們要在MAT上開啟的檔案

 

然後我們在MAT上將其開啟。

點選Finish

 

 

點選Histogram

 

 可以通過包名來分類檢視

 

從下圖中可以看到引起記憶體洩漏的類,Objects那一列不為0的就是,與我們之前看到的一樣。

 

MainActivity右鍵

 

選擇圖中的選項,意思是過濾掉虛引用,軟引用,弱引用

 

之後可以看到引用的路徑,CustomUtils裡面的instance引用了MainActivity的context,導致了MainActivity記憶體洩漏。

開啟程式碼看一下

package com.status.mattest;

import android.content.Context;

public class CustomUtils {
    private static CustomUtils instance;

    private CustomUtils(Context context) {
        this.mContext = context;
    }

    private Context mContext;

    public static CustomUtils getInstance(Context context) {
        if (instance == null) {
            instance = new CustomUtils(context);
        }

        return instance;
    }
}

 

MainActivity中:

package com.status.mattest;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        CustomUtils customUtils = CustomUtils.getInstance(this);
        textView = findViewById(R.id.tv);
        textView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(MainActivity.this, Test1Activity.class));
            }
        });
    }
}

MainActivity中呼叫了單例CustomUtils,並且把context傳了進去,而我們知道instance作為靜態物件,是GCroot引用點,所以activity關閉了它也沒法被回收,那麼最為被instance引用的Activity自然也無法被回收,所以導致了記憶體洩漏。

 

解決:

把單例模式裡面的context換為全域性application的context,也就是說單例裡面的context的週期應該與程序一樣,而不能夠與應用一樣。當我們關閉應用的時候,程序還在,並沒有被殺