使用android shell命令查看內(nèi)存使用情況
使用adb shell dumpsys meminfo pkgname或者直接使用AndroidStudio里面的memory usage功能然后就會(huì)出現(xiàn)如下信息:
Applications Memory Usage (kB):
Uptime: 14237237 Realtime: 23790474
** MEMINFO in pid 8071 [com.xtc.watch] **
Pss Private Private Swapped Heap Heap Heap
Total Dirty Clean Dirty Size Alloc Free
------ ------ ------ ------ ------ ------ ------
Native Heap 0 0 0 0 21924 8558 6405
Dalvik Heap 122472 122372 0 15672 143308 65400 77908
Dalvik Other 10361 10076 164 224
Stack 440 440 0 8
Other dev 4 0 4 0
.so mmap 6441 3452 2636 2048
.apk mmap 611 0 340 0
.ttf mmap 538 0 504 0
.dex mmap 8407 1640 2940 40
Other mmap 80 4 0 0
Unknown 10940 10936 0 148
TOTAL 160294 148920 6588 18140 165232 73958 84313
Objects
Views: 288 ViewRootImpl: 2
AppContexts: 11 Activities: 2
Assets: 5 AssetManagers: 5
Local Binders: 30 Proxy Binders: 38
Death Recipients: 3
OpenSSL Sockets: 1
SQL
MEMORY_USED: 138
PAGECACHE_OVERFLOW: 24 MALLOC_SIZE: 62
DATABASES
pgsz dbsz Lookaside(b) cache Dbname
4 20 306 25/47/12 /data/data/com.xtc.watch/databases/upload.db
- Native Heap是native層的內(nèi)存堆棧,Dalvik Heap是java層的內(nèi)存堆棧,如果這二者加起來的內(nèi)存占用超過了應(yīng)用最大內(nèi)存限制就會(huì)報(bào)OOM異常,剩下的.so mmap是 C 庫代碼占用的內(nèi)存,.jar mmap是Java 文件代碼占用的內(nèi)存 ,.apk mmap是apk代碼占用的內(nèi)存,.dex mmap是Dex 文件代碼占用的內(nèi)存
- Objects中的Activities表示當(dāng)前內(nèi)存中的activity對象的個(gè)數(shù),啟動(dòng)一個(gè)activity就會(huì)生成一個(gè)activity對象,當(dāng)退出activity的時(shí)候,activity對象就會(huì)被釋放,所以反復(fù)的進(jìn)出一個(gè)activity界面然后查看Activities的個(gè)數(shù)有沒有保持不變,如果增加了,那么就說明這個(gè)activity對象沒有被釋放,也就是說可能存在內(nèi)存泄漏,但是具體哪里泄漏了并不知道
DDMS查看內(nèi)存使用情況
eclipse中有一個(gè)ddms工具,可以查看線程信息(Threads),內(nèi)存使用情況(VM Heap),內(nèi)存分配跟蹤(Allocation Tracker),CUP使用情況(Sysinfo CUP load),內(nèi)存使用餅狀圖(Sysinfo Memory usage),這里我們暫時(shí)用到VM Heap,選擇要查看的app進(jìn)程,點(diǎn)擊左上角的show heap updates,選擇VM Heap并點(diǎn)擊Cause GC按鈕,然后就出現(xiàn)下圖:
觀察data object的Total Size選項(xiàng),這個(gè)是app的創(chuàng)建的java對象做占用的內(nèi)存大小,Count是總內(nèi)存的對象的個(gè)數(shù),反復(fù)的進(jìn)出一個(gè)activity,看data object的Total Size有沒有明顯的增加,正常情況下進(jìn)入一個(gè)activity的時(shí)候會(huì)明顯增加,退出一個(gè)activity會(huì)有明顯的回落,總體是維持在一個(gè)比較穩(wěn)定的水平如果反復(fù)進(jìn)出activity,Total Size不斷上升,那么可能就存在內(nèi)存泄漏了,需要具體排查
MAT分析內(nèi)存泄漏,用AndroidStudio的Monitors的Memory
- 多點(diǎn)擊幾下Initiate GC來回收一下可被釋放的java對象,因?yàn)閖ava的GC是定期有條件執(zhí)行的,當(dāng)內(nèi)存中只存在很少的無用對象,這時(shí)候可能并不會(huì)觸發(fā)GC,所以手動(dòng)觸發(fā)GC來保證開始檢測內(nèi)存的時(shí)候內(nèi)存都是最干凈的
-
點(diǎn)擊Dump Java Heap,然后過一會(huì)兒就會(huì)出現(xiàn)一份數(shù)據(jù)分析文件,這時(shí)候的這份數(shù)據(jù)文件是剛開始的程序?qū)ο髢?nèi)存占用情況,接下去就針對一個(gè)activity反復(fù)操作進(jìn)出等等各種反復(fù)操作,覺得差不多了,這時(shí)候就再次瘋狂點(diǎn)擊Initiate GC回收一下可是放的對象,點(diǎn)擊Dump Java Heap,這時(shí)候生成的數(shù)據(jù)分析文件就是經(jīng)過你瘋狂操作后的內(nèi)存占用情況了
這里寫圖片描述
生成的上述兩個(gè)文件右鍵,點(diǎn)擊Export to Standar .hprof導(dǎo)出到一個(gè)自己指定的目錄文件夾
-
去官網(wǎng)上面下載MAT來打開這兩個(gè)文件開始內(nèi)存分析
這里寫圖片描述
上圖中Problem Suspect部分是代表可能存在內(nèi)存泄漏的地方,Remainder表示正常的部分,再繼續(xù)往下看
這里寫圖片描述
這里寫圖片描述
上面就是對可能存在內(nèi)存泄漏部分的代碼的一個(gè)詳細(xì)的信息,可以看到有些byte數(shù)組占用了大量的內(nèi)存,keywords也是byte[],第二張圖的DexCache可能占用的較多的內(nèi)存,再點(diǎn)擊這里寫圖片描述紅色部分就會(huì)出現(xiàn)一些對象的信息列表:
這里寫圖片描述
可以輸入正則表達(dá)式來篩選你想要的類,包名下所有的類,Objects是對象的個(gè)數(shù),Shallow Heap是當(dāng)前對象所占用的內(nèi)存大小,不包括對象內(nèi)包含的對象的大小,Retained Heap表示當(dāng)前對象包括對象內(nèi)的子對象一共占用的內(nèi)存大小,所以Retained Heap會(huì)比Shallow Heap大得多,對于一些我們已知的對象在內(nèi)存不泄露的情況下,該對象的個(gè)數(shù)是確定的,所以可以通過分析Objects的個(gè)數(shù)來確定對象是否存在內(nèi)存泄漏,例如同一個(gè)Activity對象在反復(fù)進(jìn)出該Activity5次之后Objects的值為5,那就有問題了,說明同一個(gè)Activity創(chuàng)建了5個(gè)對象,正常情況下應(yīng)該是退出Activity后,對象會(huì)被回收的,所以O(shè)bjects的值應(yīng)該是0才對,而有些對象的Objects不為0并不代表一定存在內(nèi)存泄漏,例如ConnectionService是一個(gè)常駐的Service,那么它是不會(huì)被GC的,而ConnectionService里面的對象可不會(huì)被回收,所以這些對象的Objects值不為0其實(shí)就是正常的了,至于Shallow Heap和Retained Heap,我覺得可以用來分析一些對象的內(nèi)存占用,Shallow Heap一般情況下不會(huì)很大,當(dāng)你發(fā)現(xiàn)Retained Heap非常大的時(shí)候,那就說明該對象里面的對象可能占用了大量的內(nèi)存,可能存在問題;在用Objects找到可能存在內(nèi)存泄漏的對象后,右鍵List Objects,然后有兩個(gè)選項(xiàng):with outgoing references(表示的是當(dāng)前對象,引用了外部引用)和with incoming references(表示的是當(dāng)前查看的對象,被外部引用),一般當(dāng)前對象泄漏了就是對象還被外部對象持有引用,無法被釋放,所以我們選擇查看with incoming references,點(diǎn)擊with incoming references后這里寫圖片描述
這里寫圖片描述
可以看到TaskExecutor對象被外部的兩個(gè)對象所引用到了,并且可以看到引用的路徑,右鍵TaskExecutor,選擇Path to GC Roots或者或者M(jìn)erge Shortest Paths to GC Roots選項(xiàng),再選擇exclude all phantom/weak/soft .ect references排除所有的弱引用,軟引用對象尋找看有沒有存在GC Roots,如果沒有那就說明這個(gè)對象不存在內(nèi)存泄漏,最終是會(huì)被GC回收,如果存在GC Roots
這里寫圖片描述
在繼續(xù)查看GC Roots的對象是什么,其實(shí)就是被哪一個(gè)對象所引用了,上圖可以查看到被外部的sendTaskExecutor對象所引用了而sendTaskExecutor是在ConnectionService這個(gè)常駐Service中的,所以理論上是不應(yīng)該被回收的,所以這里不算是內(nèi)存泄漏,假如sendTaskExecutor是一個(gè)Activity里面的字段,而此時(shí)Activity已經(jīng)退出了,那么這時(shí)候就屬于內(nèi)存泄漏了,因?yàn)锳ctivity退出后,Activity資源包括里面的對象應(yīng)該是被回收掉的,那就找到對應(yīng)的代碼去具體分析可能造成內(nèi)存泄漏的問題所在了,這里明確一點(diǎn),存在GC Roots的不一定就一定存在內(nèi)存泄漏,GC是不會(huì)回收GC Root或者被GC Root所引用的對象的,java對象內(nèi)存泄漏其實(shí)就是對象在不用的時(shí)候仍然被其他對象持有引用導(dǎo)致GC無法回收 -
比較反復(fù)操作前的內(nèi)存信息和反復(fù)操作后的內(nèi)存信息分析前后有哪些不同這里寫圖片描述
,點(diǎn)擊紅色部分后會(huì)出現(xiàn)下圖
這里寫圖片描述
可以看到對比后的Objects和Shallow Heap信息,看Objects表示前后兩種內(nèi)存信息對于同一個(gè)對象是否有個(gè)數(shù)上的增加,因?yàn)槿绻麑ο竽鼙徽;厥?,那么開始操作前是0,操作后經(jīng)過GC應(yīng)該也是0,如果個(gè)數(shù)增加了,那就表示這個(gè)對象可能存在內(nèi)存泄漏了,拖動(dòng)到底部可看到總的比較情況
這里寫圖片描述
上圖可知,操作前的app的Objects總數(shù)比操作后的Objects少了1個(gè),同時(shí)可以看到是HttpDns這個(gè)對象的問題,于是再切換到操作后的histogram去按照上述步驟查找GC Roots,再具體分析內(nèi)存泄漏的問題
- MAT當(dāng)中還有一個(gè)dominator tree視圖,具體就不復(fù)述了,可以參考這篇大神文章MAT內(nèi)存分析