Android內存泄漏的檢測流程、捕捉以及分析


簡述:

一個APP的性能,重度關乎着用戶體驗,而關於性能檢測的一個重要方面,就是內存泄漏,通常內存泄漏的隱藏性質比較強,不同於異常導致的程序Crash,在異常導致的Crash中,我們能夠及時的發現程序問題的存在,並通過log日志定位到問題所在的具體位置,然后及時進行解決,而內存泄漏則不同,在APP中存在內存泄漏的情況下,用戶在低頻率短時間的使用中,並不能察覺到有什么異樣,反之,隨着使用頻率的提高和使用時長的增加,內存泄漏就會一直慢慢積累,消耗內存,從而會導致手機卡頓,直至APP崩潰,所以防止APP內存泄漏的出現,是至關重要的。

理論闡述內存泄漏導致的原因:

在android開發中,jvm具有自動回收的機制,會不定時不定期的去清理無用的被占用的內存,而在理論上不需要再被使用的內存,在實際中卻還持有對這一塊內存的引用,導致GC時,不會被回收釋放掉,這部分內存就會隨着程序的運行不斷堆積,從而導致應用分配的內存不夠使用導致卡頓、ANR異常等情況。

導語

關於內存泄漏的檢測,我們分為了以下幾個階段: 
1. 開發編碼過程中,在開發過程中就不斷對代碼進行內存泄漏的檢測 
2. 項目或者模塊開發完成后,對應用進行整體的內存泄漏檢測 
3. 在項目上線后,遠程端檢測項目是否存在內存泄漏的情況

一:開發編碼過程中,檢測內存泄漏

編碼過程中可能導致的內存泄漏案例分析:

關於在編碼中可能導致的泄漏案例以及編碼注意事項,本文不做過多贅述,在給到的鏈接中,作者對泄漏案例描述的都比較詳細,也比較全面,想了解的小伙伴可以【點擊這里—>內存泄漏案例

1.檢測工具:LeakCanary

首先最容日上手並且效果還不錯,那就要屬LeakCanary,效果也直觀,具體的使用配置也很簡單。

  1. 在項目的build.gradle中加入以下引用:
// 內存存泄漏檢測 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'
  • 1
  • 2
  • 3
  • 4
  1. 在application中初始化LeakCanary,到此處配置完成
/**        Explain : 初始化內存泄漏檢測
    * @author LiXaing private void initLeakCanary() { if (LeakCanary.isInAnalyzerProcess(this)) { return; } LeakCanary.install(this); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  1. 發生內存泄漏提示 
    666

當前就從網絡上獲取到了一張關於,在發生內存泄漏的時候,會在通知欄出現一個提示圖標,當點擊進去之后,就是現在展示的這張圖片,會直觀的展示內存泄漏的位置。

注意:==通過LeakCanary的使用,它可以為我們快速找到內存泄漏的位置,但並不能夠提供我們內存泄漏的原因,有的時候,內存泄漏的位置是由於其他原因導致的,本人曾經就碰到過一次,由於Fragment未能被回收,從而導致了EventBus未能解綁(在onDestory中有解綁EventBus),導致的EventBus也存在內存泄漏,而導致的原因並非是沒有解綁;所以內存泄漏的位置,並不一定就是導致泄漏的根本原因,所以后面及有可能還需要其他的工具進行輔助。==

2.檢測工具:StrictMode

StrictMode在Android 2.3(API 9)的時候就已經引入了,雖然到當前這個工具年代比較久遠了,但屬實還是非常好用的, 在開發階段使用這個工具,能夠很好的幫助發現開發中的一系列不規范的編碼,例如主線程訪問網絡,主線程讀寫磁盤,等等耗時操作,另外的一大特性就是可以幫助開發時,發現程序存在內存泄漏的情況。

StrictMode的功能主要分為兩大塊:

一塊是關於Thread,線程規范的監測,另一塊是關於VM,內存的監測。在StrictMode下分別是ThreadPolicy和VMPloicy。

ThreadPolicy:用於監測線程部分,監測 主線程中是否訪問網絡、主線程中是否讀寫磁盤等。

<!--代碼示例:在application中進行配置即可--> StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectAll()//監測所有內容 .penaltyLog()//違規對log日志 .penaltyDeath()//違規Crash .build());
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

相關API:

API 描述
detectAll 監測所以違規內容
permitAll 禁用所以監測內容
permit**…如:permitNetwork 關閉檢測網絡訪問違規
detectNetwork 監測主線程中是否存在有訪問網絡
detectDiskReads()、detectDiskWrites() 監測是否在主線程中讀寫磁盤
penaltyDeath 一旦觸發任何違規操作就直接Crash掉程序
penaltyDeathOnNetwork 一旦觸發網絡訪問違規操作就Crash掉程序
penaltyDialog 一旦觸發違規操作,就彈出違規信息對話框

當然,以上列出的是常用的API,也還有其他的一些API沒有列出來,在開發中以上的API基本夠用了,要是還有其他需求的小伙伴,就自行去查找一下吧。

VMPolicy:用於監測內存,可以監測Activity的內存泄漏,Fragment的內存泄漏(雖然內部沒有指定Fragment可用的API但內部關於類對象的檢測機制在此處就有着異曲同工之妙的作用),SQL內存泄漏,是否正常關閉讀寫流以及可以指定某個類的最大對象數目。

StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() .detectAll()//監測所以內容 .penaltyLog()//違規對log日志 .penaltyDeath()//違規Crash .build());
  • 1
  • 2
  • 3
  • 4
  • 5
API 描述
detectAll 監測所以違規內容
detectActivityLeaks 監測Activity內存泄漏的情況
detectLeakedClosableObjects() 和 detectLeakedSqlLiteObjects() 當使用的資源沒有被正確關閉時會觸發
detectLeakedRegistrationObjects 監測BroadcastReceiver 或者 ServiceConnection 注冊類對象是否被正確釋放
setClassInstanceLimit 設定某個類在內存中實例的上限
penaltyDialog 一旦觸發違規操作,就彈出違規信息對話框

當然,以上列出的是常用的API,也還有其他的一些API沒有列出來,在開發中以上的API基本夠用了,要是還有其他需求的小伙伴,就自行去查找一下吧。

二:項目或者模塊開發完成后,檢測內存泄漏

在項目或者模塊開發完成后,所需要做的就是各種測試,在交給測試人員之前,為了展現我們自身編碼的高逼格和高質量,通常我們程序猿自己先會進行一部分測試,其中很重要的一部分測試就是本章內容講的,內存泄漏的測試。

首先,我們可以使用android Studio中AndroidMonitor自帶的一個工具—>memory,這個工具也可以說是非常的好用,先簡單的介紹一下,memory雖然不可以分析出哪部分存在泄漏等情況,但可以很直觀的看到內存的占用情況,看到內存的動態變化。

memory的使用
1. 我們打開AndroidMonitor,點擊打開Memory,打開的界面,可能是這樣的什么都沒有。

這時我們需要需要注意兩個點: 
2. 可能是沒有選擇相關的進程,點擊下三角,選擇我們需要查看相關的進行。

  1. 如果還是什么都沒有,我們點擊 “Tools->Android->Enable ADBIntergration(勾選)”

memory2

操作好以上步驟之后,我們就可以看到上面這張圖片的狀態,可以很直觀的看到,應用在內存中占用的情況。

使用Memory,並不能夠分析出具體的是哪一部分存在內存泄漏,主要是用來查看內存的占用的動態情況

關於左側上方四個按鈕功能和用法的描述:

功能 描述
image Memory的開關
row 2 col 1(Initial GC) 用於手動GC,通常在抓取HPROF文件前,手動GC回收掉那些無用對象
image(Dump Java Heap) 用於生成HPROF文件,HPROF文件中的的數據是點擊這個生成按鈕這一瞬java VM執行時共享Heap中的數據,共享Heap中的數據主要包含了類的實例和數組對象,通常用於內存泄漏分析,在點擊之后就能夠看到Memory recorder在轉動,在稍許延遲之后就會生成一個HPROF文件。
image(Starg Allocation Tracking) 用於動態追蹤內存情況,可以記錄下一段時間區間內各個線程中各個方法在內存中的分配情況,使用方式:點擊Starg Allocation Tracking按鈕,開始分配追蹤,可以看到Memroy recorder在轉動,在合適的時間再點擊一下就完成了對一時間區間內內存動態的記錄,在稍許延遲之后就會生成一個.alloc文件

Dump Java Heap生成的.HPROF文件

在我們點擊 Dump Java Heap之后,就會生成一個.alloc文件,這個文件是分析內存泄漏非常重要的一部分。

image

當打開HPROF文件的時候是這樣的,什么都沒有,為了方便查找,我們點擊”Class List View”選擇–>”Package Tree View”當前展示就是變為包的視圖樹。

image

那么到了這一部分,我們應該查看哪兒是存在泄漏的呢?分為兩種情況:

1. 其實如果到了這一步,基本上可以斷定是存在內存泄漏了的,通過之前的LeakCanary、StrictMode已經可以判斷出內存泄漏了,而這部分是為了通過LeakCanary、StrictMode給出的信息更確切的確定內存泄漏的部分在內存中運作的情況和更詳細的數據。

2. ==首先需要明確的一點是LeakCanary、StrictMode並不能檢查出所有的內存泄漏==,所以在我們對應用程序反復點擊調試之后,需要我們人工審查這些類的實例,通常絕大多數類的實例例如Activity、Fragment等等每個類的實例是只有一個的,當然也是要看具體的程序實現來看待,在ViewPage中等情況就可以出現一個類有多個實例的情況,這也因實際情況來看待,如果超出了實際情況的實例對象數那么就很有可能是存在了內存泄漏。在我們人工審查這些類的實例通常都會先檢查Activity、Fragment、自定義View等等的實例情況,因為伴隨這些一起泄漏的都是高概率。

給出的上圖,是Fragmnet反復打開關閉產生實例的泄漏,實際情況中只應該存在一個,而此處有14個,有人會問,那你怎么就知道就要查找這一個Fragmnet實例呢?在此,需要說明的一點是,不管是通過上面所說的第一種情況還是第二種,都可以排查到這類泄漏的;要問:那要我沒有排查到呢?那只有說:眼瞎,就先去醫院治病~

在給出的上圖中,存在多個Fragment內存泄漏,我們就關注圖中標注好的“1”部分,我們可以通過Total Count(總實例數)和Heap Count(堆內存中實例數)可以看到有14個,點擊這個類,然后在旁邊的Instance框中我們可以看到這14個具體的實例,也就是圖中標注的“2”部分,我們點擊第一個;然后在下方的Reference Tree中我們可以看到當前這個實例對象持有的具體的對象,那么在這一部分我們怎么排查呢?我們主要需要關注的是 Dominating Size(當前指向的這個一條,在內存中占有的大小)值最大的前面幾條,為什么呢?因為泄漏導致內存無法被釋放值越大,存在泄漏的可能性越大。 所以看上圖中的“3”部分,我們針對這一條不斷的向下展開引用,在“4”部分,我們可以很清晰的看到是由EventBus導致的內存泄漏,而在我給出的這個示例里,也就是由於Fragment在銷毀時未EventBus導致的。

另外值得一說的標注的“5”,在Analyzer中有一個功能就是 Detect Leaked Activities,點擊綠色三角按鈕運行后,可以幫我們分析出當前可能存在泄漏的Activity對象。

到目前為止,我們通過HPROF文件在Studio中的使用可以完成內存泄漏的分析,不過還有更為強大的一款工具,更方便我們使用就是下要說的MAT

MAT(Memory Analyzer Tool)工具的使用

【還沒有這個工具的小伙伴可以戳這里–>Eclipse Memory Analyzer Open Source Project

首先我們需要打開在MAT中打開.hprof文件,在打開MAT工具后,點擊右上角的 “File”-> “Open Heap Dump”,然后選擇需要打開的.hprof文件,在打開的過程中可能會遇到這樣的問題:

image

關於這個問題,有兩種方式解決,比較建議的是第一種,第一種更方便,更容易操作:

一:

image

  1. 在Android Studio工具的右側,點擊上圖紅框標注的“1” CapTures;

  2. 選擇紅框標注的“2”Heap SanPshot選擇需要導出的.hprof;

  3. 選擇好需要導出的.hprof文件后,單機右鍵選擇“Export to standard”(導出標准的.hprof文件); 
    4.然后選擇導出保存到的位置,就可以了。

二: 
Windows操作系統下,打開CMD黑窗口(win鍵+R鍵,輸入cmd)

image

按照上圖的操作寫入輸入輸入路徑,然后回車,就可以完成轉換了。

接下來就是關於在MAT工具中的操作,MAT的功能很多,但就不一一介紹了,就簡單說明關於在檢測內存泄漏用到的部分。

image

選擇上圖紅框的“Histogram”,然后我們可以看到下圖界面

image

在上圖中,我們看到的是一系列的數組對象和類的實例等數據,我們可以通過上圖紅框中的部分,輸入需要查找的路徑或者類的類名等進行過濾

image

在這一部分中,所需要觀察的點,就是Objects列,是存在的實例對象數,我們選擇好了需要排查的實例對象點擊右鍵,選擇“merge shortest Path to GC Roots” -> “exclude all phantom/weak/soft etc. references”,而為什么要進行這一步過濾呢,因為導致內存泄漏的只會是硬引用,所以我們可以把虛引用,弱引用,軟引用全部進行過濾掉,可以減少誤判。

image

在進行過濾之后,我們可以看到唯一持有的硬引用只有EventBus,那么這時,我們回到項目中去具體觀察EventBus部分的代碼就好了;而這,只是在實際情況中的一種,另外在過濾掉那三種引用之后,還存在有多種引用的情況,這個時候,需要觀察的點就是“Objcets”(對象數量)和Showll Heap(在堆內存中占據的內存大小)數量越多和占據內存越高的,存在泄露的可能性越大,因為反復創建卻不能夠被回收,數量和占據的內存越高。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM