Android 性能優(yōu)化之內(nèi)存泄漏檢測(cè)以及內(nèi)存優(yōu)化(中)

上篇博客我們寫(xiě)到了 Java/Android 內(nèi)存的分配以及相關(guān) GC 的詳細(xì)分析媒咳,這篇博客我們會(huì)繼續(xù)分析 Android 中內(nèi)存泄漏的檢測(cè)以及相關(guān)案例英染,和 Android 的內(nèi)存優(yōu)化相關(guān)內(nèi)容貌亭。
  上篇:Android 性能優(yōu)化之內(nèi)存泄漏檢測(cè)以及內(nèi)存優(yōu)化(上)
  中篇:Android 性能優(yōu)化之內(nèi)存泄漏檢測(cè)以及內(nèi)存優(yōu)化(中)许赃。
  下篇:Android 性能優(yōu)化之內(nèi)存泄漏檢測(cè)以及內(nèi)存優(yōu)化(下)跟压。
  轉(zhuǎn)載請(qǐng)注明出處:http://blog.csdn.net/self_study/article/details/66969064
  對(duì)技術(shù)感興趣的同鞋加群544645972一起交流。

Android 內(nèi)存泄漏檢測(cè)

?通過(guò)上篇博客我們了解了 Android JVM/ART 內(nèi)存的相關(guān)知識(shí)和泄漏的原因塞茅,再來(lái)歸類一下內(nèi)存泄漏的源頭亩码,這里我們簡(jiǎn)單將其歸為一下三類:<ul><li>自身編碼引起</li>由項(xiàng)目開(kāi)發(fā)人員自身的編碼造成;<li>第三方代碼引起</li>這里的第三方代碼包含兩類野瘦,第三方非開(kāi)源的 SDK 和開(kāi)源的第三方框架描沟;<li>系統(tǒng)原因</li>由 Android 系統(tǒng)自身造成的泄漏,如像 WebView鞭光、InputMethodManager 等引起的問(wèn)題吏廉,還有某些第三方 ROM 存在的問(wèn)題。</ul>

Android 內(nèi)存泄漏的定位惰许,檢測(cè)與修復(fù)

?內(nèi)存泄漏不像閃退的 BUG席覆,排查起來(lái)相對(duì)要困難一些,比較極端的情況是當(dāng)你的應(yīng)用 OOM 才發(fā)現(xiàn)存在內(nèi)存泄漏問(wèn)題汹买,到了這種情況才去排查處理問(wèn)題的話佩伤,對(duì)用戶的影響就太大了,為此我們應(yīng)該在編碼階段盡早地發(fā)現(xiàn)問(wèn)題卦睹,而不是拖到上線之后去影響用戶體驗(yàn)畦戒,下面總結(jié)一下常用內(nèi)存泄漏的定位和檢測(cè)工具:

Lint

Lint 是 Android studio 自帶的靜態(tài)代碼分析工具,使用起來(lái)也很方便结序,選中需要掃描的 module障斋,然后點(diǎn)擊頂部菜單欄 Analyze -> Inspect Code ,選擇需要掃描的地方即可:


這里寫(xiě)圖片描述

      
這里寫(xiě)圖片描述

?
這里寫(xiě)圖片描述

最后在 Performance 里面有一項(xiàng)是 Handler reference leaks徐鹤,里面列出來(lái)了可能由于內(nèi)部 Handler 對(duì)象持有外部 Activity 引用導(dǎo)致內(nèi)存泄漏的地方垃环,這些地方都可以根據(jù)實(shí)際的使用場(chǎng)景去排查一下,因?yàn)楫吘共皇敲總€(gè)內(nèi)部 Handler 對(duì)象都會(huì)導(dǎo)致內(nèi)存泄漏返敬。Lint 還可以自定義掃描規(guī)則遂庄,使用姿勢(shì)很多很強(qiáng)大,感興趣的可以去了解一下劲赠,除了 Lint 之外涛目,還有像 FindBugs秸谢、Checkstyle 等靜態(tài)代碼分析工具也是很不錯(cuò)的。

StrictMode

StrictMode 是 Android 系統(tǒng)提供的 API霹肝,在開(kāi)發(fā)環(huán)境下引入可以更早的暴露發(fā)現(xiàn)問(wèn)題給開(kāi)發(fā)者估蹄,于開(kāi)發(fā)階段解決它,StrictMode 最常被使用來(lái)檢測(cè)在主線程中進(jìn)行讀寫(xiě)磁盤(pán)或者網(wǎng)絡(luò)操作等耗時(shí)任務(wù)沫换,把這些耗時(shí)任務(wù)放置于主線程會(huì)造成主線程阻塞卡頓甚至可能出現(xiàn) ANR 臭蚁,官方例子:

 public void onCreate() {
     if (DEVELOPER_MODE) {
         StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                 .detectDiskReads()
                 .detectDiskWrites()
                 .detectNetwork()   // or .detectAll() for all detectable problems
                 .penaltyLog()
                 .build());
         StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                 .detectLeakedSqlLiteObjects()
                 .detectLeakedClosableObjects()
                 .penaltyLog()
                 .penaltyDeath()
                 .build());
     }
     super.onCreate();
 }

把上面這段代碼放在早期初始化的 Application、Activity 或者其他應(yīng)用組件的 onCreate 函數(shù)里面來(lái)啟用 StrictMode 功能讯赏,一般 StrictMode 只是在測(cè)試環(huán)境下啟用垮兑,到了線上環(huán)境就不要開(kāi)啟這個(gè)功能。啟用 StrictMode 之后漱挎,在 logcat 過(guò)濾日志的地方加上 StrictMode 的過(guò)濾 tag系枪,如果發(fā)現(xiàn)一堆紅色告警的 log,說(shuō)明可能就出現(xiàn)了內(nèi)存泄漏或者其他的相關(guān)問(wèn)題了:


這里寫(xiě)圖片描述

比如上面這個(gè)就是因?yàn)檎{(diào)用 registerReceiver 之后忘記調(diào)用 unRegisterReceiver 導(dǎo)致的 activity 泄漏识樱,根據(jù)錯(cuò)誤信息便可以定位和修復(fù)問(wèn)題嗤无。

LeakCanary

? LeakCanary 是一個(gè) Android 內(nèi)存泄漏檢測(cè)的神器,正確使用可以大大減少內(nèi)存泄漏和 OOM 問(wèn)題怜庸,地址:

https://github.com/square/leakcanary

集成 LeakCanary 也很簡(jiǎn)單当犯,在 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 ExampleApplication extends Application {

  @Override public void onCreate() {
    super.onCreate();
    if (LeakCanary.isInAnalyzerProcess(this)) {
      // This process is dedicated to LeakCanary for heap analysis.
      // You should not init your app in this process.
      return;
    }
    LeakCanary.install(this);
    // Normal app init code...
  }
}

上面兩步做完之后就算是集成了 LeakCanary 了,非常簡(jiǎn)單方便割疾,如果程序出現(xiàn)了內(nèi)存泄漏會(huì)彈出 notification嚎卫,點(diǎn)擊這個(gè) notification 就會(huì)進(jìn)入到下面這個(gè)界面,或者集成 LeakCanary 之后在桌面會(huì)有一個(gè) LeakCanary 的圖標(biāo)宏榕,點(diǎn)擊進(jìn)去是所有的內(nèi)存泄漏列表拓诸,點(diǎn)擊其中一項(xiàng)同樣是進(jìn)入到下面界面:

這里寫(xiě)圖片描述

這個(gè)界面就會(huì)詳細(xì)展示引用持有鏈,一目了然麻昼,對(duì)于問(wèn)題的解決方便了很多奠支,堪稱神器,更多實(shí)用姿勢(shì)可以看看 LeakCanary FAQ抚芦。
  ?還有一點(diǎn)需要提到的是倍谜,LeakCanary 在檢測(cè)內(nèi)存泄漏的時(shí)候會(huì)阻塞主界面,這是一點(diǎn)體驗(yàn)有點(diǎn)不爽的地方叉抡,但是這時(shí)候阻塞肯定是必要的尔崔,因?yàn)榇藭r(shí)必須要掛起線程來(lái)獲取當(dāng)前堆的狀態(tài)。然后也并不是每個(gè) LeakCanary 提示的地方都有內(nèi)存泄漏褥民,這時(shí)候可能需要借助 MAT 等工具去具體分析季春。不過(guò) LeakCanary 有一點(diǎn)非常好的地方是因?yàn)?Android 系統(tǒng)也會(huì)有一些內(nèi)存泄漏,而 LeakCanary 對(duì)此則提供了一個(gè) AndroidExcludedRefs 類來(lái)幫助我們排除這些問(wèn)題消返。

Android Memory Monitor

?Memory Monitor 是 Android Studio 自帶的一個(gè)監(jiān)控內(nèi)存使用狀態(tài)的工具载弄,入口如下所示:


這里寫(xiě)圖片描述

在 Android Monitor 點(diǎn)開(kāi)之后 logcat 的右側(cè)就是 Monitor 工具耘拇,其中可以檢測(cè)內(nèi)存、CPU侦锯、網(wǎng)絡(luò)等內(nèi)容驼鞭,我們這里只用到了 Memory Monitor 功能,點(diǎn)擊紅色箭頭所指的區(qū)域尺碰,就會(huì) dump 此時(shí)此刻的 Memory 信息,并且生成一個(gè) .hprof 文件译隘,dump 完成之后會(huì)自動(dòng)打開(kāi)這個(gè)文件的顯示界面亲桥,如果沒(méi)有打開(kāi),可以通過(guò)點(diǎn)擊最左側(cè)的 Capture 界面或者 Tool Window 里面的 Capture 進(jìn)入 dump 的 .hprof 文件列表:


這里寫(xiě)圖片描述

  ?接著我們來(lái)分析一下這個(gè)生成的 .hprof 文件所展示的信息:
這里寫(xiě)圖片描述

首先左上角的下拉框固耘,可以選擇 App Heap题篷、Image Heap 和 Zygote Heap,對(duì)應(yīng)的就是上篇博客講到的 Allocation Space厅目,Image Space 和 Zygote Space番枚,我們這里選擇 Allocation Space,然后第二個(gè)選擇 PackageTreeView 這一項(xiàng)损敷,展開(kāi)之后就能看見(jiàn)一個(gè)樹(shù)形結(jié)構(gòu)了筹麸,然后繼續(xù)展開(kāi)我們應(yīng)用包名的對(duì)應(yīng)對(duì)象汽畴,就可以很清晰的看到有多少個(gè) Activity 對(duì)象了,上面那兩欄展示的信息按照從左到右的順序,定義如下所示:

Column Description
Class Name 占有這塊內(nèi)存的類名
Total Count 未被處理的數(shù)量
Heap Count 在上面選擇的指定 heap 中的數(shù)量
Sizeof 這個(gè)對(duì)象的大小橄唬,如果在變化中,就顯示 0
Shallow Size 在當(dāng)前這個(gè) heap 中的所有該對(duì)象的總數(shù)
Retained Size 這個(gè)類的所有對(duì)象占有的總內(nèi)存大小
Instance 這個(gè)類的指定對(duì)象
Reference Tree 指向這個(gè)選中對(duì)象的引用夏漱,還有指向這個(gè)引用的引用
Depth 從 GC Root 到該對(duì)象的引用鏈路的最短步數(shù)
Shallow Size 這個(gè)引用的大小
Dominating Size 這個(gè)引用占有的內(nèi)存大小

然后可以點(diǎn)擊展開(kāi)右側(cè)的 Analyzer Tasks 項(xiàng)淑掌,勾選上需要檢測(cè)的任務(wù),然后系統(tǒng)就會(huì)給你分析出結(jié)果:

這里寫(xiě)圖片描述

從分析的結(jié)果可以看到泄漏的 Activity 有兩個(gè)挥等,非常直觀友绝,然后點(diǎn)開(kāi)其中一個(gè),觀察下面的 ReferenceTree 選項(xiàng):
這里寫(xiě)圖片描述

可以看到 Thread 對(duì)象持有了 SecondActivity 對(duì)象的引用肝劲,也就是 GC Root 持有了該 Activity 的引用迁客,導(dǎo)致這個(gè) Activity 無(wú)法回收,問(wèn)題的根源我們就發(fā)現(xiàn)了涡相,接下來(lái)去處理它就好了哲泊。
  ?關(guān)于更多 Android Memory Monitor 的使用可以去看看這個(gè)官方文檔:HPROF Viewer and Analyzer

MAT

MAT(Memory Analyzer Tools)是一個(gè) Eclipse 插件催蝗,它是一個(gè)快速切威、功能豐富的 JAVA heap 分析工具,它可以幫助我們查找內(nèi)存泄漏和減少內(nèi)存消耗丙号,MAT 插件的下載地址:Eclipse Memory Analyzer Open Source Project先朦,上面通過(guò) Android studio 生成的 .hprof 文件因?yàn)楦袷缴杂胁煌衷孕枰?jīng)過(guò)一個(gè)簡(jiǎn)單的轉(zhuǎn)換,然后就可以通過(guò) MAT 去打開(kāi)了:

這里寫(xiě)圖片描述

通過(guò) MAT 去打開(kāi)轉(zhuǎn)換之后的這個(gè)文件:
這里寫(xiě)圖片描述

用的最多的就是 Histogram 功能喳魏,點(diǎn)擊 Actions 下的 Histogram 項(xiàng)就可以得到 Histogram 結(jié)果:
這里寫(xiě)圖片描述

我們可以在左上角寫(xiě)入一個(gè)正則表達(dá)式棉浸,然后就可以對(duì)所有的 Class Name 進(jìn)行篩選了,很方便刺彩,頂欄展示的信息 "Objects" 代表該類名對(duì)象的數(shù)量迷郑,剩下的 "Shallow Heap" 和 "Retained Heap" 則和 Android Memory Monitor 類似。咱們接著點(diǎn)擊 SecondActivity创倔,然后右鍵:
這里寫(xiě)圖片描述

在彈出來(lái)的菜單中選擇 List objects->with incoming references 將該類的實(shí)例全部列出來(lái):
這里寫(xiě)圖片描述

通過(guò)這個(gè)列表我們可以看到 SecondActivity@0x12faa900 這個(gè)對(duì)象被一個(gè) this$00x12c65140 的匿名內(nèi)部類對(duì)象持有嗡害,然后展開(kāi)這一項(xiàng),發(fā)現(xiàn)這個(gè)對(duì)象是一個(gè) handler 對(duì)象:
這里寫(xiě)圖片描述

快速定位找到這個(gè)對(duì)象沒(méi)有被釋放的原因畦攘,可以右鍵 Path to GC Roots->exclude all phantom/weak/soft etc. references 來(lái)顯示出這個(gè)對(duì)象到 GC Root 的引用鏈霸妹,因?yàn)閺?qiáng)引用才會(huì)導(dǎo)致對(duì)象無(wú)法釋放,所以這里我們要排除其他三種引用:
這里寫(xiě)圖片描述

這么處理之后的結(jié)果就很明顯了:
這里寫(xiě)圖片描述

一個(gè)非常明顯的強(qiáng)引用持有鏈知押,GC Root 我們前面的博客中說(shuō)到包含了線程叹螟,所以這里的 Thread 對(duì)象 GC Root 持有了 SecondActivity 的引用,導(dǎo)致該 Activity 無(wú)法被釋放台盯。
  ?MAT 還有一個(gè)功能就是能夠?qū)Ρ葍蓚€(gè) .hprof 文件罢绽,將兩個(gè)文件都添加到 Compare Basket 里面:
這里寫(xiě)圖片描述

添加進(jìn)去之后點(diǎn)擊右上角的 ! 按鈕爷恳,然后就會(huì)生成兩個(gè)文件的對(duì)比:
這里寫(xiě)圖片描述

同樣適用正則表達(dá)式將需要的類篩選出來(lái):
這里寫(xiě)圖片描述

結(jié)果也很明顯有缆,退出 Activity 之后該 Activity 對(duì)象未被回收,仍然在內(nèi)存中温亲,或者可以調(diào)整對(duì)比選項(xiàng)讓對(duì)比結(jié)果更加明顯:
這里寫(xiě)圖片描述

也可以對(duì)比兩個(gè)對(duì)象集合棚壁,方法與此類似,都是將兩個(gè) Dump 結(jié)果中的對(duì)象集合添加到 Compare Basket 中去對(duì)比栈虚,找出差異后用 Histogram 查詢的方法找出 GC Root袖外,定位到具體的某個(gè)對(duì)象上。

adb shell && Memory Usage

可以通過(guò)命令 adb shell dumpsys meminfo [package name] 來(lái)將指定 package name 的內(nèi)存信息打印出來(lái)魂务,這種模式可以非常直觀地看到 Activity 未釋放導(dǎo)致的內(nèi)存泄漏:

這里寫(xiě)圖片描述
這里寫(xiě)圖片描述

或者也可以通過(guò) Android studio 的 Memory Usage 功能進(jìn)行查看曼验,最后的結(jié)果是一樣的:
這里寫(xiě)圖片描述

Allocation Tracker

Android studio 還自帶一個(gè) Allocation Tracker 工具,功能和 DDMS 中的基本差不多粘姜,這個(gè)工具可以監(jiān)控一段時(shí)間之內(nèi)的內(nèi)存分配:

這里寫(xiě)圖片描述

在內(nèi)存圖中點(diǎn)擊途中標(biāo)紅的部分鬓照,啟動(dòng)追蹤,再次點(diǎn)擊就是停止追蹤孤紧,隨后自動(dòng)生成一個(gè) .alloc 文件豺裆,這個(gè)文件就記錄了這次追蹤到的所有數(shù)據(jù),然后會(huì)在右上角打開(kāi)一個(gè)數(shù)據(jù)面板:
這里寫(xiě)圖片描述

這個(gè)工具詳細(xì)的介紹可以看看這個(gè)博客:Android性能專項(xiàng)測(cè)試之Allocation Tracker(Android Studio)号显。

常見(jiàn)的內(nèi)存泄漏案例

?我們來(lái)看看常見(jiàn)的導(dǎo)致內(nèi)存泄漏的案例:

靜態(tài)變量造成的內(nèi)存泄漏

?由于靜態(tài)變量的生命周期和應(yīng)用一樣長(zhǎng)臭猜,所以如果靜態(tài)變量持有 Activity 或者 Activity 中 View 對(duì)象的應(yīng)用躺酒,就會(huì)導(dǎo)致該靜態(tài)變量一直直接或者間接持有 Activity 的引用,導(dǎo)致該 Activity 無(wú)法釋放蔑歌,從而引發(fā)內(nèi)存泄漏羹应,不過(guò)需要注意的是在大多數(shù)這種情況下由于靜態(tài)變量只是持有了一個(gè) Activity 的引用,所以導(dǎo)致的結(jié)果只是一個(gè) Activity 對(duì)象未能在退出之后釋放次屠,這種問(wèn)題一般不會(huì)導(dǎo)致 OOM 問(wèn)題园匹,只能通過(guò)上面介紹過(guò)的幾種工具在開(kāi)發(fā)中去觀察發(fā)現(xiàn)。
  這種問(wèn)題的解決思路很簡(jiǎn)單劫灶,就是不讓靜態(tài)變量直接或者間接持有 Activity 的強(qiáng)引用偎肃,可以將其修改為 soft reference 或者 weak reference 等等之類的,或者如果可以的話將 Activity Context 更換為 Application Context浑此,這樣就能保證生命周期一致不會(huì)導(dǎo)致內(nèi)存泄漏的問(wèn)題了。

內(nèi)部類持有外部類引用

我們上面的 demo 中模擬的就是內(nèi)部類對(duì)象持有外部類對(duì)象的引用導(dǎo)致外部類對(duì)象無(wú)法釋放的問(wèn)題滞详,在 Java 中非靜態(tài)內(nèi)部類和匿名內(nèi)部類會(huì)持有他們所屬外部類對(duì)象的引用凛俱,如果這個(gè)非靜態(tài)內(nèi)部類對(duì)象或者匿名內(nèi)部類對(duì)象被一個(gè)耗時(shí)的線程(或者其他 GC Root)直接或者間接的引用,甚至這些內(nèi)部類對(duì)象本身就在做一些耗時(shí)操作料饥,這樣就會(huì)導(dǎo)致這個(gè)內(nèi)部類對(duì)象直接或者間接無(wú)法釋放蒲犬,內(nèi)部類對(duì)象無(wú)法釋放,外部類的對(duì)象也就無(wú)法釋放造成內(nèi)存泄漏岸啡,而且如果無(wú)法釋放的對(duì)象積累起來(lái)就會(huì)造成 OOM原叮,示例代碼如下所示:

public class SecondActivity extends AppCompatActivity{
    private Handler handler;
    private Bitmap bitmap;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.pic);//decode 一個(gè)大圖來(lái)模擬內(nèi)存無(wú)法釋放導(dǎo)致的崩潰
        findViewById(R.id.btn_second).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                finish();
            }
        });

        handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);

            }
        };
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                handler.sendEmptyMessage(0);
            }
        }).start();
    }
}

這個(gè)問(wèn)題的解決方法可以根據(jù)實(shí)際情況進(jìn)行選擇:<ul><li>將非靜態(tài)內(nèi)部類或者匿名內(nèi)部類修改為靜態(tài)內(nèi)部類,比如 Handler 修改為靜態(tài)內(nèi)部類巡蘸,然后讓 Handler 持有外部 Activity 的一個(gè) Weak Reference 或者 Soft Reference奋隶;</li><li>在 Activity 頁(yè)面銷毀的時(shí)候?qū)⒑臅r(shí)任務(wù)停止,這樣就能保證 GC Root 不會(huì)間接持有 Activity 的引用悦荒,也就不會(huì)導(dǎo)致內(nèi)存泄漏唯欣;</li></ul>

錯(cuò)誤使用 Activity Context

這個(gè)很好理解,在一個(gè)錯(cuò)誤的地方使用 Activity Context搬味,造成 Activity Context 被靜態(tài)變量長(zhǎng)時(shí)間引用導(dǎo)致無(wú)法釋放而引發(fā)的內(nèi)存泄漏境氢,這個(gè)問(wèn)題的處理方式也很簡(jiǎn)單,如果可以的話修改為 Application Context 或者將強(qiáng)引用變成其他引用碰纬。

資源對(duì)象沒(méi)關(guān)閉造成的內(nèi)存泄漏

資源性對(duì)象比如(Cursor萍聊,F(xiàn)ile 文件等)往往都用了一些緩沖,我們?cè)诓皇褂玫臅r(shí)候應(yīng)該及時(shí)關(guān)閉它們悦析,以便它們的緩沖對(duì)象被及時(shí)回收寿桨,這些緩沖不僅存在于 java 虛擬機(jī)內(nèi),還存在于 java 虛擬機(jī)外她按,如果我們僅僅是把它的引用設(shè)置為 null 而不關(guān)閉它們牛隅,往往會(huì)造成內(nèi)存泄漏炕柔。但是有些資源性對(duì)象,比如 SQLiteCursor(在析構(gòu)函數(shù) finalize()媒佣,如果我們沒(méi)有關(guān)閉它匕累,它自己會(huì)調(diào) close() 關(guān)閉),如果我們沒(méi)有關(guān)閉它系統(tǒng)在回收它時(shí)也會(huì)關(guān)閉它默伍,但是這樣的效率太低了欢嘿。因此對(duì)于資源性對(duì)象在不使用的時(shí)候,應(yīng)該調(diào)用它的 close() 函數(shù)也糊,將其關(guān)閉掉炼蹦,然后再置為 null,在我們的程序退出時(shí)一定要確保我們的資源性對(duì)象已經(jīng)關(guān)閉狸剃。
  程序中經(jīng)常會(huì)進(jìn)行查詢數(shù)據(jù)庫(kù)的操作掐隐,但是經(jīng)常會(huì)有使用完畢 Cursor 后沒(méi)有關(guān)閉的情況,如果我們的查詢結(jié)果集比較小钞馁,對(duì)內(nèi)存的消耗不容易被發(fā)現(xiàn)虑省,只有在常時(shí)間大量操作的情況下才會(huì)出現(xiàn)內(nèi)存問(wèn)題,這樣就會(huì)給以后的測(cè)試和問(wèn)題排查帶來(lái)困難和風(fēng)險(xiǎn)僧凰,示例代碼:

Cursor cursor = getContentResolver().query(uri...); 
if (cursor.moveToNext()) { 
... ... 
} 

更正代碼:

Cursor cursor = null;
try {
    cursor = getContentResolver().query(uri...);
    if (cursor != null && cursor.moveToNext()) {
        ... ...
    }
} finally {
    if (cursor != null) {
        try {
            cursor.close();
        } catch (Exception e) {
            //ignore this
        }
    }
}

集合中對(duì)象沒(méi)清理造成的內(nèi)存泄漏

在實(shí)際開(kāi)發(fā)過(guò)程中難免會(huì)有把對(duì)象添加到集合容器(比如 ArrayList)中的需求探颈,如果在一個(gè)對(duì)象使用結(jié)束之后未將該對(duì)象從該容器中移除掉,就會(huì)造成該對(duì)象不能被正確回收训措,從而造成內(nèi)存泄漏伪节,解決辦法當(dāng)然就是在使用完之后將該對(duì)象從容器中移除。

WebView造成的內(nèi)存泄露

具體的可以看看我的這篇博客:android WebView詳解绩鸣,常見(jiàn)漏洞詳解和安全源碼(下)怀大。

未取消注冊(cè)導(dǎo)致的內(nèi)存泄漏

一些 Android 程序可能引用我們的 Android 程序的對(duì)象(比如注冊(cè)機(jī)制),即使我們的 Android 程序已經(jīng)結(jié)束了全闷,但是別的應(yīng)用程序仍然還持有對(duì)我們 Android 程序某個(gè)對(duì)象的引用叉寂,這樣也會(huì)造成內(nèi)存不能被回收,比如調(diào)用 registerReceiver 后未調(diào)用unregisterReceiver总珠。假設(shè)我們希望在鎖屏界面(LockScreen)中屏鳍,監(jiān)聽(tīng)系統(tǒng)中的電話服務(wù)以獲取一些信息,則可以在 LockScreen 中定義一個(gè) PhoneStateListener 的對(duì)象局服,同時(shí)將它注冊(cè)到 TelephonyManager 服務(wù)中钓瞭,對(duì)于 LockScreen 對(duì)象,當(dāng)需要顯示鎖屏界面的時(shí)候就會(huì)創(chuàng)建一個(gè) LockScreen 對(duì)象淫奔,而當(dāng)鎖屏界面消失的時(shí)候 LockScreen 對(duì)象就會(huì)被釋放掉山涡,但是如果在釋放 LockScreen 對(duì)象的時(shí)候忘記取消我們之前注冊(cè)的 PhoneStateListener 對(duì)象,則會(huì)間接導(dǎo)致 LockScreen 無(wú)法被回收,如果不斷的使鎖屏界面顯示和消失鸭丛,則最終會(huì)由于大量的 LockScreen 對(duì)象沒(méi)有辦法被回收而引起 OOM竞穷,雖然有些系統(tǒng)程序本身好像是可以自動(dòng)取消注冊(cè)的(當(dāng)然不及時(shí)),但是我們還是應(yīng)該在程序結(jié)束時(shí)明確的取消注冊(cè)鳞溉。

因?yàn)閮?nèi)存碎片導(dǎo)致分配內(nèi)存不足

還有一種情況是因?yàn)轭l繁的內(nèi)存分配和釋放瘾带,導(dǎo)致內(nèi)存區(qū)域里面存在很多碎片,當(dāng)這些碎片足夠多熟菲,new 一個(gè)大對(duì)象的時(shí)候看政,所有的碎片中沒(méi)有一個(gè)碎片足夠大以分配給這個(gè)對(duì)象,但是所有的碎片空間加起來(lái)又是足夠的時(shí)候抄罕,就會(huì)出現(xiàn) OOM允蚣,而且這種 OOM 從某種意義上講,是完全能夠避免的呆贿。
  由于產(chǎn)生內(nèi)存碎片的場(chǎng)景很多嚷兔,從 Memory Monitor 來(lái)看,下面場(chǎng)景的內(nèi)存抖動(dòng)是很容易產(chǎn)生內(nèi)存碎片的:


這里寫(xiě)圖片描述

最常見(jiàn)產(chǎn)生內(nèi)存抖動(dòng)的例子就是在 ListView 的 getView 方法中未復(fù)用 convertView 導(dǎo)致 View 的頻繁創(chuàng)建和釋放做入,針對(duì)這個(gè)問(wèn)題的處理方式那當(dāng)然就是復(fù)用 convertView谴垫;或者是 String 拼接創(chuàng)建大量小的對(duì)象(比如在一些頻繁調(diào)用的地方打字符串拼接的 log 的時(shí)候);如果是其他的問(wèn)題母蛛,就需要通過(guò) Memory Monitor 去觀察內(nèi)存的實(shí)時(shí)分配釋放情況,找到內(nèi)存抖動(dòng)的地方修復(fù)它乳怎,或者如果當(dāng)出現(xiàn)下面這種情況下的 OOM 時(shí)彩郊,也是由于內(nèi)存碎片導(dǎo)致無(wú)法分配內(nèi)存:


這里寫(xiě)圖片描述

出現(xiàn)上面這種類型的 Crash 時(shí)就要去分析應(yīng)用里面是不是存在大量分配釋放對(duì)象的地方了。

Android 內(nèi)存優(yōu)化

內(nèi)存優(yōu)化請(qǐng)看下篇:Android 性能優(yōu)化之內(nèi)存泄漏檢測(cè)以及內(nèi)存優(yōu)化(下)蚪缀。

引用

http://blog.csdn.net/luoshengyang/article/details/42555483
http://blog.csdn.net/luoshengyang/article/details/41688319
http://blog.csdn.net/luoshengyang/article/details/42492621
http://blog.csdn.net/luoshengyang/article/details/41338251
http://blog.csdn.net/luoshengyang/article/details/41581063
https://mp.weixin.qq.com/s?__biz=MzA4MzEwOTkyMQ==&mid=2667377215&idx=1&sn=26e3e9ec5f4cf3e7ed1e90a0790cc071&chksm=84f32371b384aa67166a3ff60e3f8ffdfbeed17b4c8b46b538d5a3eec524c9d0bcac33951a1a&scene=0&key=c2240201df732cf062d22d3cf95164740442d817864520af90bb0e71fa51102f2e91475a4f597ec20653c59d305c8a3e518d3f575d419dfcf8fb63a776e0d9fa6d3a9a6a52e84fedf3f467fe4af1ba8b&ascene=0&uin=Mjg5MDI3NjQ2Mg%3D%3D&devicetype=iMac+MacBookPro11%2C4+OSX+OSX+10.12.3+build(16D32)&version=12010310&nettype=WIFI&fontScale=100&pass_ticket=Upl17Ws6QQsmZSia%2F%2B0xkZs9DYxAJBQicqh8rcaxYUjcu3ztlJUPxYrQKML%2BUtuf
http://geek.csdn.net/news/detail/127226
http://www.reibang.com/p/216b03c22bb8
https://zhuanlan.zhihu.com/p/25213586
https://joyrun.github.io/2016/08/08/AndroidMemoryLeak/
http://www.cnblogs.com/larack/p/6071209.html
https://source.android.com/devices/tech/dalvik/gc-debug.html
http://blog.csdn.net/high2011/article/details/53138202
http://gityuan.com/2015/10/03/Android-GC/
http://www.ayqy.net/blog/android-gc-log%E8%A7%A3%E8%AF%BB/
https://developer.android.com/studio/profile/investigate-ram.html
https://zhuanlan.zhihu.com/p/26043999

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末秫逝,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子询枚,更是在濱河造成了極大的恐慌违帆,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,013評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件金蜀,死亡現(xiàn)場(chǎng)離奇詭異刷后,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)渊抄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)尝胆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人护桦,你說(shuō)我怎么就攤上這事含衔。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,370評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵贪染,是天一觀的道長(zhǎng)缓呛。 經(jīng)常有香客問(wèn)我,道長(zhǎng)杭隙,這世上最難降的妖魔是什么哟绊? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,168評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮寺渗,結(jié)果婚禮上匿情,老公的妹妹穿的比我還像新娘。我一直安慰自己信殊,他們只是感情好炬称,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,153評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著涡拘,像睡著了一般玲躯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鳄乏,一...
    開(kāi)封第一講書(shū)人閱讀 48,954評(píng)論 1 283
  • 那天跷车,我揣著相機(jī)與錄音,去河邊找鬼橱野。 笑死朽缴,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的水援。 我是一名探鬼主播密强,決...
    沈念sama閱讀 38,271評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蜗元!你這毒婦竟也來(lái)了或渤?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,916評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤奕扣,失蹤者是張志新(化名)和其女友劉穎薪鹦,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體惯豆,經(jīng)...
    沈念sama閱讀 43,382評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡池磁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,877評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了楷兽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片框仔。...
    茶點(diǎn)故事閱讀 37,989評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖拄养,靈堂內(nèi)的尸體忽然破棺而出离斩,到底是詐尸還是另有隱情银舱,我是刑警寧澤,帶...
    沈念sama閱讀 33,624評(píng)論 4 322
  • 正文 年R本政府宣布跛梗,位于F島的核電站寻馏,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏核偿。R本人自食惡果不足惜诚欠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,209評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望漾岳。 院中可真熱鬧轰绵,春花似錦、人聲如沸尼荆。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,199評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)捅儒。三九已至液样,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間巧还,已是汗流浹背鞭莽。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,418評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留麸祷,地道東北人澎怒。 一個(gè)月前我還...
    沈念sama閱讀 45,401評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像阶牍,于是被迫代替她去往敵國(guó)和親丹拯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,700評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容