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