內存泄露實例分析 -- Android內存優化第四彈

引言

前文內存分析工具集中介紹了一系列的內存分析工具及其基本使用, 諸如Memory Monitor, HPROF Viewer, MAT等等. 實際上了解了工具的使用, 咱們就已經掌握瞭如何分析內存問題了.javascript

爲了能對工具的使用更加深刻, 本篇將一個代碼片斷爲例, 從時序的角度講解下如何使用這些工具來分析一個內存泄露.html

系列文:
1.GC那些事兒
2.Android的內存管理
3.內存分析工具
4.內存泄露實例分析java

1, 例子

假設有一個單例的ListenerManager, 能夠add / remove Listener, 有一個Activity, 實現了該listener, 且這個Activity中持有大對象BigObject, BigObject中包含一個大的字符串數組和一個Bitmap List.android

代碼片斷以下:git

ListenerManager程序員

public class ListenerManager {

    private static ListenerManager sInstance;
    private ListenerManager() {}

    private List<SampleListener> listeners = new ArrayList<>();

    public static ListenerManager getInstance() {
        if (sInstance == null) {
            sInstance = new ListenerManager();
        }

        return sInstance;
    }

    public void addListener(SampleListener listener) {
        listeners.add(listener);
    }

    public void removeListener(SampleListener listener) {
        listeners.remove(listener);
    }
}複製代碼

MemoryLeakActivitygithub

public class MemoryLeakActivity extends AppCompatActivity implements SampleListener {

    private BigObject mBigObject = new BigObject();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_memory_leak);

        ListenerManager.getInstance().addListener(this);
    }

    @Override
    public void doSomething() {

    }
}複製代碼

具體代碼參看Github.數組

2, 使用Android Studio的自帶工具來分析

根據前文的工具介紹, Android Studio自帶了Memory Monitor, HPROF Viewer & Analyzer來分析內存使用及內存問題.android-studio

2.1 查看Memory使用, 並導出hprof文件

啓動咱們要檢測的Activity(MemoryLeakActivity), 而後退出, 在monitor中查看內存變化:
app

2.2 在HPROF Viewer界面, 開始分析

第一步

點擊"Analyzer Tasks"視圖中的啓動按鈕, 啓動分析

第二步

查看"Analysis Result"中的分析結果, 點擊"Leaked Activityes"中的具體實例, 該實例的引用關係將會展現在"Reference Tree"視圖中.

第三步

根據"Reference Tree"視圖中的引用關係找到是誰讓這個leak的activity活着的, 也就是誰Dominate這個activity對象.

此例中, 比較簡單, 能夠很清晰看到是ListenerManager的靜態單例sInstance最終支配了MemoryLeakActivity. sIntance鏈接到GC Roots, 故而致使MemoryLeakActivity GC Roots可達, 沒法被回收.

關於Dominate, GC Roots, GC Roots可達, 活對象等概念, 請結合前兩篇的理論文1, 2觀看.

2.3 使用Heap Viewer查看內存消耗

上述步驟, 可讓咱們快速定位可能的內存泄露. 固然, 內存問題, 除了內存泄露, 還有內存消耗過大. 咱們能夠在Heap Viewer中查看分析內存的消耗點, 以下:

3, MAT讓咱們看的更多

就單純的分析Android App的內存使用和內存泄露來講, 我的以爲Android Studio自帶的工具已經足夠好了, 並且再持續變得更好, 也更便於Android的開發人員去理解. 故而其實一開始在Android性能分析工具一文中, 我就沒有詳細去提MAT. 相對與Android Studio中的Memory Monitor, HPROF工具來講, MAT的使用顯得更加生澀, 難以理解.

關於MAT的幫助文檔, 我的翻譯了一份, 須要的同窗戳這裏.

固然, 若是咱們想對內存的使用相關知識瞭解得更多, 仍是有必要了解下MAT的...
下面咱們以幾個角度來了解下MAT的基本使用:

再次:
Android Studio導出的hprof文件須要轉換下才能夠在MAT中使用.

$ hprof-conv com.anly.samples_2016.10.31_15.07.hprof mat.hprof複製代碼

3.1 Histogram視圖定位內存消耗

MAT中不少視圖的第一行, 均可以輸入正則, 來匹配咱們關注的對象實例.

3.2 Dominate Tree視圖查看支配關係

3.3 使用OQL查詢相關對象

對於Android App開發來講, 大部分的內存問題都跟四大組件, 尤爲是Activity相關, 故而咱們會想查出全部Activity實例的內存佔用狀況, 可使用OQL來查詢:

具體OQL語法看這裏.

3.4 GC路徑定位問題

上面幾個視圖均可以讓咱們很快速的找到內存的消耗點, 接下來咱們要分析的就是爲什麼這些個大對象沒有被回收.

根據第一彈:GC那些事兒所言, 對象沒有被回收是由於他有到GC Roots的可達路徑. 那麼咱們就來分析下這條路徑(Path to GC Roots), 看看是誰在這條路中"搭橋".

以下, 進入該對象的"path2gc"視圖:

Slice 1

一樣, 與HPROF Analyzer殊途同歸, 找出了是ListenerManager的靜態實例致使了MemoryLeakActivity沒法回收.

4, LeakCanary讓內存泄露無處可藏

大道至簡, 程序員都應該"懶", 故而咱們都但願有更方便快捷的方式讓咱們發現內存泄露. 偉大的square發揮了這一優良傳統, LeakCanary面世.

4.1 加入LeakCanary

app的build.gradle中加入:

dependencies {
   debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
   releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
   testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
 }複製代碼

Application中加入:

public class SampleApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        LeakCanary.install(this);
    }
}複製代碼

4.2 操做要檢測的界面, 查看結果

當發生可疑內存泄露時, 會在桌面生成一個"Leaks"的圖標, 點擊進去能夠看到內存泄露的疑點報告:

能夠看到, 結果與前兩者的分析結果"驚人"一致, 不一致就出事兒了, :)

足夠方便且直觀吧, 趕快用起來吧.
固然, 內存問題不單單是內存泄露, 還有內存佔用過多等, 這時咱們就須要藉助前兩種工具了.

結語

綜上, 建議LeakCanary集成做爲App的必選項, 大多數狀況下咱們能夠用LeakCanary結合Android Studio自帶的工具分析內存問題.

若是有精力, 仍是建議深刻了解下MAT, 能讓咱們更深刻了解GC的機制, 相關概念, MAT還有不少更高端的功能值得咱們探索, 例如Heap比較等.

其實, 內存問題的分析, 無外乎分析對象的內存佔用(Retained Size), 找出Retained Size大的對象, 找到其直接支配(Immediate Dominator), 跟蹤其GC可達路徑(Path to GC Roots), 從而找到是誰讓這個大對象活着. 找到問題癥結, 對症下藥.

相關文章
相關標籤/搜索