從早期G1的192MB RAM開始,到如今動輒1G -2G RAM的設備,爲單個App分配的內存從16MB到48MB甚至更多,但OOM從未曾離咱們遠去。這是由於大部分App中圖片內容佔據了50%甚至75%以上,而App內容的極大豐富,所需的圖片愈來愈多,屏幕尺寸也愈來愈大分辨率也愈來愈高,所需的圖片的大小也跟着往上漲,這在大屏手機和平板上尤爲明顯。並且還常常要兼容低版本的設備。因此Android的內存管理顯得極爲重要。android
在這裏咱們主要講兩件事情:
1.Gingerbread和Honeycomb中的一些影響你使用內存的變化
-heap size
-GC
-bitmaps
2.理解heap的用途分配
-logs
-merory leaks
-Eclispe Memory Analyzer(MAT)數組
首先第一部分,咱們都知道Android是個多任務操做系統,同時運行着不少程序,都須要分配內存,不可能爲一個程序分配愈來愈多的內存以致於讓整個系統都崩潰,所以heap的大小有個硬性的限制,跟設備相關,從發展來講也是愈來愈大,G1:16MB,Droid:24MB,Nexus One:32MB,Xoom:48MB,可是一旦超出了這個使用的範圍,OOM便產生了。若是你正在開發一個應用,想知道設備的heap大小的限制是多少,比方說根據這個值來估算本身應用的緩存大小應該限制在什麼樣一個水平,你可使用ActivityManager.getMemoryClass ()來得到一個單位爲MB的整數值,通常來講最低很多於16MB,對於如今的設備而言這個值會愈來愈大,24MB,32MB,48MB甚至更大。瀏覽器
可是對於一些內存很是吃緊的好比圖片瀏覽器等應用,在平板上所需的內存更大了。所以在Honeycomb以後AndroidManifest.xml增長了largeHeap的選項緩存
1 2 3 4 |
<application
android:largeHeap="true"
...
</application>
|
這容許你的應用使用更多的heap,能夠用ActivityManager.getLargeMemoryClass ()返回一個更大的可用heap size。可是這裏要警告的是,千萬不要由於你的應用報OOM了而使用這個選項,由於更大的heap size意味着更多的GC時間,意味着應用的性能愈來愈差,並且用戶也會發現其餘應用頗有可能會內存不足。只有你須要使用不少的內存並且很是瞭解每一部份內存的用途,這些所需的內存都是不可或缺的,這個時候你才應該使用這個選項。併發
剛剛咱們提到更大的heap size意味着更多的GC時間,下面咱們來談談Garbage Collection。app
如上圖所示,GC會選擇一些它瞭解還存活的對象做爲內存遍歷的根節點,比方說thread stack中的變量,JNI中的全局變量,zygote中的對象等,而後開始對heap進行遍歷。到最後,部分沒有直接或者間接引用到GC Roots的就是須要回收的垃圾,會被GC回收掉。以下圖藍色部分。eclipse
所以也能夠看出,更大的heap size須要遍歷的對象更多,回收垃圾的時間更長,因此說使用largeHeap選項會致使更多的GC時間。ide
在Gingerbread以前,GC執行的時候整個應用會暫停下來執行全面的垃圾回收,所以有時候會看到應用卡頓的時間比較長,通常來講>100ms,對用戶而言已經足以察覺出來。Gingerbread及以上的版本,GC作了很大的改進,基本上能夠說是併發的執行,也不是執行徹底的回收,只有在GC開始以及結束的時候會有很是短暫的停頓時間,通常來講<5ms,用戶也不會察覺到。工具
在Honeycomb以前,Bitmap的內存分配以下圖。性能
藍色部分是Dalvik heap,黃色部分是Bitmap引用對象的堆內存,而Bitmap實際的Pixel Data是分配在Native Memory中。這樣作有幾個問題,首先須要調用reclyce()來代表Bitmap的Pixel Data佔用的內存可回收,不調用這個方法的話就要靠finalizer來讓GC回收這部份內存,但瞭解finalizer的應該都知道這至關的不可靠;其次是很難進行Debug,由於一些內存分析工具是查看不到Native Memory的;再次就是不調用reclyce()須要回收Native Memory中的內存的話會致使一次完整的GC,GC執行的時候會暫停整個應用。
Honeycomb以後,Bitmap的內存分配作出了改變,以下圖
藍色黃色部分沒有變化,但Bitmap實際的Pixel Data的內存也一樣分配在了Dalvik heap中。這樣作有幾個好處。首先能同步的被GC回收掉;其次Debug變得容易了,由於內存分析工具可以查看到這部分的內存;再次就是GC變成併發了,可作部分的回收,也就是極大縮短了GC執行時暫停的時間。
接下來咱們講第二部分。通常來講咱們但願瞭解咱們應用內存分配,最基本的就是查看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以及觸發GC的緣由。幾種GC類型:
- GC_CONCURRENT:這是由於你的heap內存佔用開始往上漲了,爲了不heap內存滿了而觸發執行的。
- GC_FOR_MALLOC:這是因爲concurrent gc沒有及時執行完而你的應用又須要分配更多的內存,內存要滿了,這個時候不得不停下來進行malloc gc。
- GC_EXTERNAL_ALLOC:這是爲external分配的內存執行的GC,也就是上文提到的Bitmap Pixel Data之類的。
- GC_HPROF_DUMP_HEAP:這是當你作HPROF這樣一個操做去建立一個HPROF profile的時候執行的。
- GC_EXPLICIT:這是因爲你顯式的調用了System.gc(),這是不提倡的,通常來講咱們能夠信任系統的GC。
freed 2049K代表在此次GC中回收了多少內存。
65% free 3571K/9991K是heap的一些統計數據,代表此次回收後65%的heap可用,存活的對象大小3571K,heap大小是9991K。
external 4703K/5261K是Native Memory的數據。放Bitmap Pixel Data或者是NIO Direct Buffer之類的。第一個數字代表Native Memory中已分配了多少內存,第二個值有點相似一個浮動的閥值,代表分配內存達到這個值系統就會觸發一次GC進行內存回收。
paused 2ms 2ms代表GC暫停的時間。從這裏你能夠看到越大的heap size你須要暫停的時間越長。若是是concurrent gc你會看到2個時間一個開始一個結束,這時間是很短的,但若是是其餘類型的GC,你極可能只會看到一個時間,而這個時間是相對比較長的。
經過Log能夠對內存信息有個基本的瞭解,但這不足以瞭解什麼對象在使用內存,在哪使用了內存。這時候你須要用Heap Dumps。一個Heap Dump基本上來講就是一個包含你heap中全部對象信息的二進制文件。你能夠用DDMS來生成這個文件,點擊DDMS中下圖的那個按鈕。
同時Heap Dumps也有對應的API,你想要在特定的時間點獲取一份Heap Dump,使用android.os.Debug.dumpHprofData()。獲取到的文件要轉換成標準的HPROF格式,使用以下命令:hprof-conv orig.hprof converted.hprof。而後用MAT或者jhat進行分析。
在講MAT以前先講下Memory Leaks。要清楚GC並不能防止Memory Leaks,所謂Memory Leaks就是引用到了已經沒用的對象從而讓這些對象避免了被GC回收,跟C/C++中的概念並不同。容易致使內存泄漏的是一些Activity,Context,View,Drawable之類的引用,和一些非靜態的內部類比方說Runnable之類的以及一些Caches。好比你旋轉屏幕的時候在新的方向上產生一個新的Activity,若是有變量引用到舊的Activity就會致使其沒法被GC,形成Memory Leaks。
一般經過上面介紹的Log信息,只要已用memory一直處於上升的情形而不回落,便大體能瞭解到應用存在Memory Leaks。不過MAT這類工具能夠幫助你更好的對memory進行分析。使用MAT以前有2個概念是要掌握的:Shallow heap和Retained heap。Shallow heap表示對象自己所佔內存大小,一個內存大小100bytes的對象Shallow heap就是100bytes。Retained heap表示經過回收這一個對象總共能回收的內存,比方說一個100bytes的對象還直接或者間接地持有了另外3個100bytes的對象引用,回收這個對象的時候若是另外3個對象沒有其餘引用也能被回收掉的時候,Retained heap就是400bytes。
MAT使用Dominator Tree這樣一種來自圖形理論的概念。
所謂Dominator,就是Flow Graph中從源節點出發到某個節點的的必經節點。那麼根據這個概念咱們能夠從上圖左側的Flow Graph構造出右側的Dominator Tree。這樣一來很容易就看出每一個節點的Retained heap了。Shallow heap和Retained heap在MAT中是很是有用的概念,用於內存泄漏的分析。
咱們用Honeycomb3.0中的HoneycombGallery作一個Demo。在工程的MainActivity當中加入以下代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class MainActivity extends Activity implements ActionBar.TabListener {
static Leaky leak = null;
class Leaky {
void doSomething() {
System.out.println("Wheee!!!");
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (leak == null) {
leak = new Leaky();
}
...
|
上面這段代碼,對Java熟悉的同窗都應該瞭解內部類對象持有了外部類對象引用,而leak做爲靜態變量在非空判斷下只產生了一個對象,所以當旋轉屏幕時生成新的Activity的時候舊的Activity的引用依然被持有,以下圖:
經過觀察旋轉屏幕先後Log中GC的信息也能看出heap的分配往上漲了許多,而且在GC執行完heap的分配穩定以後並無降下來,這就是內存泄漏的跡象。
咱們經過MAT來進行分析。先下載MAT,能夠做爲Eclipse插件下載,也能夠做爲RCP應用下載,本質上沒有區別。DDMS中選中應用對應的進程名,點擊Dump HPROF file的按鈕,等一小段時間生成HPROF文件,若是是Eclipse插件的話,Eclipse會爲這個HPROF自動轉化成標準的HPROF並自動打開MAT分析界面。若是是做爲RCP應用的話,須要用sdk目錄tools中的hprof-conv工具來進行轉化,也就是上文說起的命令hprof-conv orig.hprof converted.hprof,這種方式保存HPROF文件的位置選擇更爲自主,你也能夠修改Eclipse的設置讓Eclipse提示保存而不是自動打開,在Preferences -> Android -> DDMS中的HPROF Action由Open in Eclipse改成Save to disk。打開MAT,選擇轉化好的HPROF文件,能夠看到Overview的界面以下圖:
中間的餅狀圖就是根據咱們上文所說的Retained heap的概念獲得的內存中一些Retained Size最大的對象。點擊餅狀圖能看到這些對象類型,但對內存泄漏的分析還遠遠不夠。再看下方Action中有Dominator Tree和Histogram的選項,這通常來講是最有用的工具。還記得咱們上文說過的Dominator Tree的概念嗎,這就是咱們用來跟蹤內存泄漏的方式。點開Dominator Tree,會看到以Retained heap排序的一系列對象,以下圖:
Resources類型對象因爲通常是系統用於加載資源的,因此Retained heap較大是個比較正常的狀況。但咱們注意到下面的Bitmap類型對象的Retained heap也很大,頗有多是因爲內存泄漏形成的。因此咱們右鍵點擊這行,選擇Path To GC Roots ->exclude weak references,能夠看到下圖的情形:
Bitmap最終被leak引用到,這應該是一種不正常的現象,內存泄漏極可能就在這裏了。MAT不會告訴哪裏是內存泄漏,須要你自行分析,因爲這是Demo,是咱們特地形成的內存泄漏,所以比較容易就能看出來,真實的應用場景可能須要你仔細的進行分析。
根據咱們上文介紹的Dominator的概念,leak對象是該Bitmap對象的Dominator,應該出如今Dominator Tree視圖裏面,但實際上卻沒有。這是因爲MAT並無對weak references作區別對待,這也是咱們選擇exclude weak references的緣由。若是咱們Path To GC Roots ->with all references,咱們能夠看到下圖的情形:
能夠看到還有另一個對象在引用着這個Bitmap對象,瞭解weak references的同窗應該知道GC是如何處理weak references,所以在內存泄漏分析的時候咱們能夠把weak references排除掉。
有些同窗可能但願根據某種類型的對象個數來分析內存泄漏。咱們在Overview視圖中選擇Actions -> Histogram,能夠看到相似下圖的情形:
上圖展現了內存中各類類型的對象個數和Shallow heap,咱們看到byte[]佔用Shallow heap最多,那是由於Honeycomb以後Bitmap Pixel Data的內存分配在Dalvik heap中。右鍵選中byte[]數組,選擇List Objects -> with incoming references,能夠看到byte[]具體的對象列表:
咱們發現第二個byte[]的Retained heap較大,內存泄漏的可能性較大,所以右鍵選中這行,Path To GC Roots -> exclude weak references,一樣能夠看到上文所提到的狀況,咱們的Bitmap對象被leak所引用到,這裏存在着內存泄漏。
在Histogram視圖中第一行<Regex>中輸入com.example.android.hcgallery,過濾出咱們本身應用中的類型,以下圖:
咱們發現本應該只有一個MainActivity如今卻有兩個,顯然不正常。右鍵選擇List Objects -> with incoming references,能夠看到這兩個具體的MainActivity對象。右鍵選中Retained heap較大的MainActivity,Path To GC Roots -> exclude weak references,再一次可疑對象又指向了leak對象。
以上是MAT一些基本的用法,若是你感興趣,能夠自行深刻的去了解MAT的其餘功能。