從早期G1的192MB RAM開始,到現(xiàn)在動(dòng)輒1G -2G RAM的設(shè)備,為單個(gè)App分配的內(nèi)存從16MB到48MB甚至更多,但OOM從不曾離我們遠(yuǎn)去。這是因?yàn)榇蟛糠諥pp中圖片內(nèi)容占據(jù)了50%甚至75%以上,而App內(nèi)容的極大豐富,所需的圖片越來(lái)越多,屏幕尺寸也越來(lái)越大分辨率也越來(lái)越高,所需的圖片的大小也跟著往上漲,這在大屏手機(jī)和平板上尤其明顯。而且還經(jīng)常要兼容低版本的設(shè)備。所以Android的內(nèi)存管理顯得極為重要。
在這里我們主要講兩件事情:
1.Gingerbread和Honeycomb中的一些影響你使用內(nèi)存的變化
-heap size
-GC
-bitmaps
2.理解heap的用途分配
-logs
-merory leaks
-Eclispe Memory Analyzer(MAT)
首先第一部分,我們都知道Android是個(gè)多任務(wù)操作系統(tǒng),同時(shí)運(yùn)行著很多程序,都需要分配內(nèi)存,不可能為一個(gè)程序分配越來(lái)越多的內(nèi)存以至于讓整個(gè)系統(tǒng)都崩潰,因此heap的大小有個(gè)硬性的限制,跟設(shè)備相關(guān),從發(fā)展來(lái)說(shuō)也是越來(lái)越大,G1:16MB,Droid:24MB,Nexus One:32MB,Xoom:48MB,但是一旦超出了這個(gè)使用的范圍,OOM便產(chǎn)生了。如果你正在開發(fā)一個(gè)應(yīng)用,想知道設(shè)備的heap大小的限制是多少,比方說(shuō)根據(jù)這個(gè)值來(lái)估算自己應(yīng)用的緩存大小應(yīng)該限制在什么樣一個(gè)水平,你可以使用ActivityManager.getMemoryClass ()來(lái)獲得一個(gè)單位為MB的整數(shù)值,一般來(lái)說(shuō)最低不少于16MB,對(duì)于現(xiàn)在的設(shè)備而言這個(gè)值會(huì)越來(lái)越大,24MB,32MB,48MB甚至更大。
但是對(duì)于一些內(nèi)存非常吃緊的比如圖片瀏覽器等應(yīng)用,在平板上所需的內(nèi)存更大了。因此在Honeycomb之后AndroidManifest.xml增加了largeHeap的選項(xiàng)
這允許你的應(yīng)用使用更多的heap,可以用ActivityManager.getLargeMemoryClass ()返回一個(gè)更大的可用heap size。但是這里要警告的是,千萬(wàn)不要因?yàn)槟愕膽?yīng)用報(bào)OOM了而使用這個(gè)選項(xiàng),因?yàn)楦蟮膆eap size意味著更多的GC時(shí)間,意味著應(yīng)用的性能越來(lái)越差,而且用戶也會(huì)發(fā)現(xiàn)其他應(yīng)用很有可能會(huì)內(nèi)存不足。只有你需要使用很多的內(nèi)存而且非常了解每一部分內(nèi)存的用途,這些所需的內(nèi)存都是不可或缺的,這個(gè)時(shí)候你才應(yīng)該使用這個(gè)選項(xiàng)。
剛剛我們提到更大的heap size意味著更多的GC時(shí)間,下面我們來(lái)談?wù)凣arbage Collection。
如上圖所示,GC會(huì)選擇一些它了解還存活的對(duì)象作為內(nèi)存遍歷的根節(jié)點(diǎn),比方說(shuō)thread stack中的變量,JNI中的全局變量,zygote中的對(duì)象等,然后開始對(duì)heap進(jìn)行遍歷。到最后,部分沒(méi)有直接或者間接引用到GC Roots的就是需要回收的垃圾,會(huì)被GC回收掉。如下圖藍(lán)色部分。
因此也可以看出,更大的heap size需要遍歷的對(duì)象更多,回收垃圾的時(shí)間更長(zhǎng),所以說(shuō)使用largeHeap選項(xiàng)會(huì)導(dǎo)致更多的GC時(shí)間。
在Gingerbread之前,GC執(zhí)行的時(shí)候整個(gè)應(yīng)用會(huì)暫停下來(lái)執(zhí)行全面的垃圾回收,因此有時(shí)候會(huì)看到應(yīng)用卡頓的時(shí)間比較長(zhǎng),一般來(lái)說(shuō)>100ms,對(duì)用戶而言已經(jīng)足以察覺(jué)出來(lái)。Gingerbread及以上的版本,GC做了很大的改進(jìn),基本上可以說(shuō)是并發(fā)的執(zhí)行,也不是執(zhí)行完全的回收,只有在GC開始以及結(jié)束的時(shí)候會(huì)有非常短暫的停頓時(shí)間,一般來(lái)說(shuō)<5ms,用戶也不會(huì)察覺(jué)到。
在Honeycomb之前,Bitmap的內(nèi)存分配如下圖。
藍(lán)色部分是Dalvik heap,黃色部分是Bitmap引用對(duì)象的堆內(nèi)存,而Bitmap實(shí)際的Pixel Data是分配在Native Memory中。這樣做有幾個(gè)問(wèn)題,首先需要調(diào)用reclyce()來(lái)表明Bitmap的Pixel Data占用的內(nèi)存可回收,不調(diào)用這個(gè)方法的話就要靠finalizer來(lái)讓GC回收這部分內(nèi)存,但了解finalizer的應(yīng)該都知道這相當(dāng)?shù)牟豢煽?;其次是很難進(jìn)行Debug,因?yàn)橐恍﹥?nèi)存分析工具是查看不到Native Memory的;再次就是不調(diào)用reclyce()需要回收Native Memory中的內(nèi)存的話會(huì)導(dǎo)致一次完整的GC,GC執(zhí)行的時(shí)候會(huì)暫停整個(gè)應(yīng)用。
Honeycomb之后,Bitmap的內(nèi)存分配做出了改變,如下圖
藍(lán)色黃色部分沒(méi)有變化,但Bitmap實(shí)際的Pixel Data的內(nèi)存也同樣分配在了Dalvik heap中。這樣做有幾個(gè)好處。首先能同步的被GC回收掉;其次Debug變得容易了,因?yàn)閮?nèi)存分析工具能夠查看到這部分的內(nèi)存;再次就是GC變成并發(fā)了,可做部分的回收,也就是極大縮短了GC執(zhí)行時(shí)暫停的時(shí)間。
接下來(lái)我們講第二部分。一般來(lái)說(shuō)我們希望了解我們應(yīng)用內(nèi)存分配,最基本的就是查看Log信息。比方說(shuō)看這樣一個(gè)Log信息(這是Gingerbread版本的,Honeycomb以后log信息有改動(dòng)):
D/dalvikvm( 9050): GC_CONCURRENT freed 2049K, 65% free 3571K/
9991K, external 4703K/5261K, paused 2ms 2ms
GC_XXX表明是哪類GC以及觸發(fā)GC的原因。幾種GC類型:
- GC_CONCURRENT:這是因?yàn)槟愕膆eap內(nèi)存占用開始往上漲了,為了避免heap內(nèi)存滿了而觸發(fā)執(zhí)行的。
- GC_FOR_MALLOC:這是由于concurrent gc沒(méi)有及時(shí)執(zhí)行完而你的應(yīng)用又需要分配更多的內(nèi)存,內(nèi)存要滿了,這個(gè)時(shí)候不得不停下來(lái)進(jìn)行malloc gc。
- GC_EXTERNAL_ALLOC:這是為external分配的內(nèi)存執(zhí)行的GC,也就是上文提到的Bitmap Pixel Data之類的。
- GC_HPROF_DUMP_HEAP:這是當(dāng)你做HPROF這樣一個(gè)操作去創(chuàng)建一個(gè)HPROF profile的時(shí)候執(zhí)行的。
- GC_EXPLICIT:這是由于你顯式的調(diào)用了System.gc(),這是不提倡的,一般來(lái)說(shuō)我們可以信任系統(tǒng)的GC。
freed 2049K表明在這次GC中回收了多少內(nèi)存。
65% free 3571K/9991K是heap的一些統(tǒng)計(jì)數(shù)據(jù),表明這次回收后65%的heap可用,存活的對(duì)象大小3571K,heap大小是9991K。
external 4703K/5261K是Native Memory的數(shù)據(jù)。放Bitmap Pixel Data或者是NIO Direct Buffer之類的。第一個(gè)數(shù)字表明Native Memory中已分配了多少內(nèi)存,第二個(gè)值有點(diǎn)類似一個(gè)浮動(dòng)的閥值,表明分配內(nèi)存達(dá)到這個(gè)值系統(tǒng)就會(huì)觸發(fā)一次GC進(jìn)行內(nèi)存回收。
paused 2ms 2ms表明GC暫停的時(shí)間。從這里你可以看到越大的heap size你需要暫停的時(shí)間越長(zhǎng)。如果是concurrent gc你會(huì)看到2個(gè)時(shí)間一個(gè)開始一個(gè)結(jié)束,這時(shí)間是很短的,但如果是其他類型的GC,你很可能只會(huì)看到一個(gè)時(shí)間,而這個(gè)時(shí)間是相對(duì)比較長(zhǎng)的。
通過(guò)Log可以對(duì)內(nèi)存信息有個(gè)基本的了解,但這不足以了解什么對(duì)象在使用內(nèi)存,在哪使用了內(nèi)存。這時(shí)候你需要用Heap Dumps。一個(gè)Heap Dump基本上來(lái)說(shuō)就是一個(gè)包含你heap中所有對(duì)象信息的二進(jìn)制文件。你可以用DDMS來(lái)生成這個(gè)文件,點(diǎn)擊DDMS中下圖的那個(gè)按鈕。
同時(shí)Heap Dumps也有對(duì)應(yīng)的API,你想要在特定的時(shí)間點(diǎn)獲取一份Heap Dump,使用android.os.Debug.dumpHprofData()。獲取到的文件要轉(zhuǎn)換成標(biāo)準(zhǔn)的HPROF格式,使用如下命令:hprof-conv orig.hprof converted.hprof。然后用MAT或者jhat進(jìn)行分析。
在講MAT之前先講下Memory Leaks。要清楚GC并不能防止Memory Leaks,所謂Memory Leaks就是引用到了已經(jīng)沒(méi)用的對(duì)象從而讓這些對(duì)象避免了被GC回收,跟C/C++中的概念并不一樣。容易導(dǎo)致內(nèi)存泄漏的是一些Activity,Context,View,Drawable之類的引用,和一些非靜態(tài)的內(nèi)部類比方說(shuō)Runnable之類的以及一些Caches。比如你旋轉(zhuǎn)屏幕的時(shí)候在新的方向上產(chǎn)生一個(gè)新的Activity,如果有變量引用到舊的Activity就會(huì)導(dǎo)致其無(wú)法被GC,造成Memory Leaks。
通常通過(guò)上面介紹的Log信息,只要已用memory一直處于上升的情形而不回落,便大致能了解到應(yīng)用存在Memory Leaks。不過(guò)MAT這類工具可以幫助你更好的對(duì)memory進(jìn)行分析。使用MAT之前有2個(gè)概念是要掌握的:Shallow heap和Retained heap。Shallow heap表示對(duì)象本身所占內(nèi)存大小,一個(gè)內(nèi)存大小100bytes的對(duì)象Shallow heap就是100bytes。Retained heap表示通過(guò)回收這一個(gè)對(duì)象總共能回收的內(nèi)存,比方說(shuō)一個(gè)100bytes的對(duì)象還直接或者間接地持有了另外3個(gè)100bytes的對(duì)象引用,回收這個(gè)對(duì)象的時(shí)候如果另外3個(gè)對(duì)象沒(méi)有其他引用也能被回收掉的時(shí)候,Retained heap就是400bytes。
MAT使用Dominator Tree這樣一種來(lái)自圖形理論的概念。
所謂Dominator,就是Flow Graph中從源節(jié)點(diǎn)出發(fā)到某個(gè)節(jié)點(diǎn)的的必經(jīng)節(jié)點(diǎn)。那么根據(jù)這個(gè)概念我們可以從上圖左側(cè)的Flow Graph構(gòu)造出右側(cè)的Dominator Tree。這樣一來(lái)很容易就看出每個(gè)節(jié)點(diǎn)的Retained heap了。Shallow heap和Retained heap在MAT中是非常有用的概念,用于內(nèi)存泄漏的分析。
我們用Honeycomb3.0中的HoneycombGallery做一個(gè)Demo。在工程的MainActivity當(dāng)中加入如下代碼:
上面這段代碼,對(duì)Java熟悉的同學(xué)都應(yīng)該了解內(nèi)部類對(duì)象持有了外部類對(duì)象引用,而leak作為靜態(tài)變量在非空判斷下只產(chǎn)生了一個(gè)對(duì)象,因此當(dāng)旋轉(zhuǎn)屏幕時(shí)生成新的Activity的時(shí)候舊的Activity的引用依然被持有,如下圖:
通過(guò)觀察旋轉(zhuǎn)屏幕前后Log中GC的信息也能看出heap的分配往上漲了許多,并且在GC執(zhí)行完heap的分配穩(wěn)定之后并沒(méi)有降下來(lái),這就是內(nèi)存泄漏的跡象。
我們通過(guò)MAT來(lái)進(jìn)行分析。先下載MAT,可以作為Eclipse插件下載,也可以作為RCP應(yīng)用下載,本質(zhì)上沒(méi)有區(qū)別。DDMS中選中應(yīng)用對(duì)應(yīng)的進(jìn)程名,點(diǎn)擊Dump HPROF file的按鈕,等一小段時(shí)間生成HPROF文件,如果是Eclipse插件的話,Eclipse會(huì)為這個(gè)HPROF自動(dòng)轉(zhuǎn)化成標(biāo)準(zhǔn)的HPROF并自動(dòng)打開MAT分析界面。如果是作為RCP應(yīng)用的話,需要用sdk目錄tools中的hprof-conv工具來(lái)進(jìn)行轉(zhuǎn)化,也就是上文提及的命令hprof-conv orig.hprof converted.hprof,這種方式保存HPROF文件的位置選擇更為自主,你也可以修改Eclipse的設(shè)置讓Eclipse提示保存而不是自動(dòng)打開,在Preferences -> Android -> DDMS中的HPROF Action由Open in Eclipse改為Save to disk。打開MAT,選擇轉(zhuǎn)化好的HPROF文件,可以看到Overview的界面如下圖:
中間的餅狀圖就是根據(jù)我們上文所說(shuō)的Retained heap的概念得到的內(nèi)存中一些Retained Size最大的對(duì)象。點(diǎn)擊餅狀圖能看到這些對(duì)象類型,但對(duì)內(nèi)存泄漏的分析還遠(yuǎn)遠(yuǎn)不夠。再看下方Action中有Dominator Tree和Histogram的選項(xiàng),這一般來(lái)說(shuō)是最有用的工具。還記得我們上文說(shuō)過(guò)的Dominator Tree的概念嗎,這就是我們用來(lái)跟蹤內(nèi)存泄漏的方式。點(diǎn)開Dominator Tree,會(huì)看到以Retained heap排序的一系列對(duì)象,如下圖:
Resources類型對(duì)象由于一般是系統(tǒng)用于加載資源的,所以Retained heap較大是個(gè)比較正常的情況。但我們注意到下面的Bitmap類型對(duì)象的Retained heap也很大,很有可能是由于內(nèi)存泄漏造成的。所以我們右鍵點(diǎn)擊這行,選擇Path To GC Roots ->exclude weak references,可以看到下圖的情形:
Bitmap最終被leak引用到,這應(yīng)該是一種不正常的現(xiàn)象,內(nèi)存泄漏很可能就在這里了。MAT不會(huì)告訴哪里是內(nèi)存泄漏,需要你自行分析,由于這是Demo,是我們特意造成的內(nèi)存泄漏,因此比較容易就能看出來(lái),真實(shí)的應(yīng)用場(chǎng)景可能需要你仔細(xì)的進(jìn)行分析。
根據(jù)我們上文介紹的Dominator的概念,leak對(duì)象是該Bitmap對(duì)象的Dominator,應(yīng)該出現(xiàn)在Dominator Tree視圖里面,但實(shí)際上卻沒(méi)有。這是由于MAT并沒(méi)有對(duì)weak references做區(qū)別對(duì)待,這也是我們選擇exclude weak references的原因。如果我們Path To GC Roots ->with all references,我們可以看到下圖的情形:
可以看到還有另外一個(gè)對(duì)象在引用著這個(gè)Bitmap對(duì)象,了解weak references的同學(xué)應(yīng)該知道GC是如何處理weak references,因此在內(nèi)存泄漏分析的時(shí)候我們可以把weak references排除掉。
有些同學(xué)可能希望根據(jù)某種類型的對(duì)象個(gè)數(shù)來(lái)分析內(nèi)存泄漏。我們?cè)贠verview視圖中選擇Actions -> Histogram,可以看到類似下圖的情形:
上圖展示了內(nèi)存中各種類型的對(duì)象個(gè)數(shù)和Shallow heap,我們看到byte[]占用Shallow heap最多,那是因?yàn)镠oneycomb之后Bitmap Pixel Data的內(nèi)存分配在Dalvik heap中。右鍵選中byte[]數(shù)組,選擇List Objects -> with incoming references,可以看到byte[]具體的對(duì)象列表:
我們發(fā)現(xiàn)第二個(gè)byte[]的Retained heap較大,內(nèi)存泄漏的可能性較大,因此右鍵選中這行,Path To GC Roots -> exclude weak references,同樣可以看到上文所提到的情況,我們的Bitmap對(duì)象被leak所引用到,這里存在著內(nèi)存泄漏。
在Histogram視圖中第一行<Regex>中輸入com.example.android.hcgallery,過(guò)濾出我們自己應(yīng)用中的類型,如下圖:
我們發(fā)現(xiàn)本應(yīng)該只有一個(gè)MainActivity現(xiàn)在卻有兩個(gè),顯然不正常。右鍵選擇List Objects -> with incoming references,可以看到這兩個(gè)具體的MainActivity對(duì)象。右鍵選中Retained heap較大的MainActivity,Path To GC Roots -> exclude weak references,再一次可疑對(duì)象又指向了leak對(duì)象。
以上是MAT一些基本的用法,如果你感興趣,可以自行深入的去了解MAT的其他功能。
http://blog.csdn.net/chengyingzhilian/article/details/8662849
聯(lián)系客服