Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android內存分析之MAT

Android內存分析之MAT

編輯:關於Android編程

  面試中經常會問到內存優化,我們在開發過程中也多少會遇到OOM的問題,根據大牛們的博客,記錄下我的學習思路

一、為何會OOM?

1. 一直以來Andorid手機的內存都比iPhone(iPhone6RAM1G)大的多,Android卻經常出現OOM,這是為何?

 這個是因為Android系統對dalvik的vm heapsize 作了硬性限制,當java進程申請的java空間超過閥值時,就會拋出OOM異常(這個閥值可以是48M、24M、16M等,視機型而定),可以通過adb shell getprop | grep dalvik.vm.heapgrowthlimit查看此值。(我的一加2 Android6.0.1已經達到了256M)

 也就是說,程序發生OOM並不表示RAM不足,而是因為程序申請的java heap對象超過了dalvik.vm.heapgrowthlimit。也就是說,在RAM充足的情況下,也可能發生OOM

 這樣的設計似乎有些不合理,但是Google為什麼這樣做呢?這樣設計的目的是為了讓Android系統能同時讓比較多的進程常駐內存,這樣程序啟動時就不用每次都重新加載到內存,能夠給用戶更快的響應。迫使每個應用程序使用較小的內存,移動設備非常有限的RAM就能使比較多的app常駐其中。

2. 大型游戲如何在較小的heapsize上運行?

創建子進程

創建一個新的進程,那麼我們就可以把一些對象分配到新進程的heap上了,從而達到一個應用程序使用更多的內存的目的,當然,創建子進程會增加系統開銷,而且並不是所有應用程序都適合這樣做,視需求而定。

創建子進程的方法:使用android:process標簽

使用jni在native heap上申請空間(推薦使用)

nativeheap的增長並不受dalvik vm heapsize的限制,從圖6可以看出這一點,它的native heap size已經遠遠超過了dalvik heap size的限制。

只要RAM有剩余空間,程序員可以一直在native heap上申請空間,當然如果 RAM快耗盡,memory killer會殺進程釋放RAM。大家使用一些軟件時,有時候會閃退,就可能是軟件在native層申請了比較多的內存導致的。比如,我(余龍飛)就碰到過UC web在浏覽內容比較多的網頁時閃退,原因就是其native heap增長到比較大的值,占用了大量的RAM,被memory killer殺掉了。(Fresco使用的就是這種方式)

使用顯存(操作系統預留RAM的一部分作為顯存)

使用OpenGL textures等API,texture memory不受dalvik vm heapsize限制,這個我沒有實踐過。再比如Android中GraphicBufferAllocator申請的內存就是顯存。

3. Android內存究竟如何?(native heap、java heap)

進程的地址空間

在32位操作系統中(Native Process),進程的地址空間為0到4GB:

圖一vc+086Os0ruw486qvLiw2U1Ctb28uEdCoaPV/crH0vLOqkhlYXC/1bzk08mzzNDy1LG53MDto6zL+dLUyN3S17P2z9bKudPDsru1sbW81sLRz9bYzsrM4qGjDQo8cD5BbmRyb2lk1tC1xL34s8w8L3A+DQo8cD5uYXRpdmW9+LPMo7qyydPDQy9DKyvKtc/Wo6yyu7D8uqxkYWx2aWvKtcD9tcRsaW51eL34s8yjrC9zeXN0ZW0vYmluL8S/wrzPwsPmtcSzzNDyzsS8/tTL0NC687a8ysfS1G5hdGl2Zb34s8zQzsq9tObU2rXEoaPI58/Czbwvc3lzdGVtL2Jpbi9zdXJmYWNlZmxpbmdlcqGiL3N5c3RlbS9iaW4vcmlsZKGicHJvY3Jhbmu1yL7NysduYXRpdmW9+LPMoaM8L3A+DQo8cD5qYXZhvfizzKO6yrXA/buvwctkYWx2aWvQ6cTiu/rKtcD9tcRsaW51eL34s8yjrL34s8y1xMjrv9ptYWluuq/K/c6qamF2Ybqvyv2ho2RhbHZpa9DpxOK7+sq1wP21xMve1ve9+LPMysdmb3JrKCnPtc2ztffTw7S0vai1xGxpbnV4vfizzKOsy/nS1MO/0ru49mFuZHJvaWTJz7XEamF2Yb34s8zKtbzKyc++zcrH0ru49mxpbnV4vfizzKOs1rvKx734s8zW0LbgwcvSu7j2ZGFsdmlr0OnE4rv6yrXA/aGj0vK0y6OsamF2Yb34s8y1xMTatOa31sXkschuYXRpdmW9+LPMuLTU06GjyOfNvDOjrEFuZHJvaWTPtc2z1tC1xNOm08OzzNDyu/mxvra8ysdqYXZhvfizzKOsyOfXwMPmoaK157uwoaLBqs+1yMuhote0zKzAuLXItcihozxiciAvPg0KoaE8aW1nIGFsdD0="這裡寫圖片描述" src="/uploadfile/Collfiles/20160429/20160429090459627.jpg" title="\" />
 

Android中進程的堆內存

第一張圖和下面這張圖分別介紹了native process和java process的結構,這個是我們需要深刻理解的,進程空間中的heap空間是我們需要重點關注的。heap空間完全由程序員控制,我們使用的malloc、C++ new和java new所申請的空間都是heap空間, C/C++申請的內存空間在native heap中,而java申請的內存空間則在dalvik heap中。

這裡寫圖片描述

注:Java中的code segment,data segment,heap,stack
stack(棧):對象引用都是在棧裡的,相當於C/C++的指針
heap(堆):new出來的對象實例才是在堆裡
data segment:一般存放常量和靜態常量
code segment:方法,函數什麼的都是放在code segment

Bitmap分配在native heap還是dalvik heap上?

 3.0後是分配在dalvik heap上,和3.x之前是分配在native heap

4. 以上主要來自:現任支付寶大神余龍飛著作——Android進程的內存管理分析

二、內存分析之MAT

1. 谷歌提供了幾種內存檢測工具:

Memory Monitor:內存監視器
這裡寫圖片描述 Heap Viewer:堆查看器
這裡寫圖片描述
這裡寫圖片描述 Allocation Tracker:分配追蹤器
這裡寫圖片描述

Investigating Your RAM Usage:調查您的RAM使用

通過Log輸出的GC命令來判斷:

GC_CONCURRENT:heap快滿了
GC_FOR_MALLOC:因為你的應用程序試圖分配內存時,你已經充分引起GC堆,所以系統必須停止你的應用和回收內存。
GC_HPROF_DUMP_HEAP:GC發生時,你要創建的請求HPROF文件來分析你的堆。
GC_EXPLICIT:一個明確的GC,當收到調用gc()時出現,應該盡量避免手動調用,而是相信GC會自動清理
GC_EXTERNAL_ALLOC:只會在API10以及以下才會出現


GC的原因:
Concurrent:不會暫停應用線程,在後台運行,不會影響內存分配
Alloc:GC是因為你的應用程序試圖分配內存時,你heapwas已滿。在這種情況下,垃圾收集發生在分配線程。
Explicit:手動調用gc(),我們應該避免手動調用,我們要相信GC,手動調用會影響線程分配以及沒必要的cpu周期,還可能導致其他線程的搶占。
NativeAlloc:native內存的回收,主要來自人為造成的native內存壓力,例如:Bitmap、渲染腳本分配的對象
CollectorTransition:.....由於用到的太少,後面的就不再詳述

2. 觸發內存洩漏

多次切換屏幕的橫縱,在Activity不同狀態旋轉屏幕,然後再返回。旋轉設備往往會導致應用程序的Activity、Context、或View對象洩漏,因為系統中重新創建活動,如果程序中其他地方擁有這些對象的引用,系統無法回收。 多個應用之間切換,在Activity不同的狀態下切換(導航至主屏幕,然後返回到您的應用程序)。

3. 怎樣的內存是健康的?

內存使用率低,使用率穩定(波動小) 沒有正在使用的對象,要能夠被GC回收(避免內存洩漏)

不再使用的內存對象、或著大型內存,使用結束(虛引用)馬上回收(finalize()方法進行清理,通過 java.lang.ref.PhantomReference實現)

我們進行內存分析具體分析什麼呢?
1. 大型對象
2. 不使用的未能被釋放的對象(內存洩漏)

而谷歌目前提供的內存分析工具只能從宏觀上進行內存分析,無法針對某個對象進行分析

這裡我們這裡需要使用強大的第三方內存分析工具MAT(Memory Analyzer Tool)針對具體內存進行分析

4. MAT基礎知識

MAT簡介

MAT(Memory Analyzer Tool),一個基於Eclipse的內存分析工具,是一個快速、功能豐富的JAVA heap分析工具,它可以幫助我們查找內存洩漏和減少內存消耗。使用內存分析工具從眾多的對象中進行分析,快速的計算出在內存中對象的占用大小,看看是誰阻止了垃圾收集器的回收工作,並可以通過報表直觀的查看到可能造成這種結果的對象。 當然MAT也有獨立的不依賴Eclipse的版本,只不過這個版本在調試Android內存的時候,需要將DDMS生成的文件進行轉換,才可以在獨立版本的MAT上打開。不過Android SDK中已經提供了這個Tools,所以使用起來也是很方便的。

MAT下載地址

獨立版本下載地址: https://eclipse.org/mat/downloads.php
這種方式有個麻煩的地方就是DDMS導出的文件,需要進行轉換才可以在MAT中打開。

Eclipse插件地址:http://download.eclipse.org/mat/1.5/update-site/

MAT中重要概念介紹
要看懂MAT的列表信息,Shallow heap、Retained Heap、GC Root 這幾個概念一定要弄懂。

Shallow heap
Shallow size就是對象本身占用內存的大小,不包含其引用的對象。

常規對象(非數組)的Shallow size有其成員變量的數量和類型決定。

數組的shallow size有數組元素的類型(對象類型、基本類型)和數組長度決定

因為不像c++的對象本身可以存放大量內存,java的對象成員都是些引用。真正的內存都在堆上,看起來是一堆原生的byte[],char[], int[],所以我們如果只看對象本身的內存,那麼數量都很小。所以我們看到Histogram圖是以Shallow size進行排序的,排在第一位第二位的是byte,char 。

Retained Heap

Retained Heap的概念:如果一個對象被釋放掉,那麼該對象引用的所有對象(包括被遞歸釋放的)占用的heap也會被釋放。

如果一個對象的某個成員new了一大塊int數組,那這個int數組也可以計算到這個對象中。與shallow heap比較,Retained heap可以更精確的反映一個對象實際占用的大小(因為如果該對象釋放,retained heap都可以被釋放)。

注意:A和B都引用到同一內存,A釋放時,該內存不會被釋放。所以這塊內存不會被計算到A或者B的Retained Heap中。故Retained Heap並不總是那麼有效。

這一點並不重要,因為MAT引入了Dominator Tree--對象引用樹,計算Retained Heap就會非常方便,顯示也非常方便。對應到MAT UI上,在dominator tree這個view中,顯示了每個對象的shallow heap和retained heap。然後可以以該節點為樹根,一步步的細化看看retained heap到底是用在什麼地方了。

GC Root

GC發現通過任何reference chain(引用鏈)無法訪問某個對象的時候,該對象即被回收。

這裡寫圖片描述

這裡寫圖片描述

名詞GC Roots正是分析這一過程的起點,例如JVM自己確保了對象的可到達性(那麼JVM就是GC Roots),所以GC Roots就是這樣在內存中保持對象可到達性的,一旦不可到達,即被回收。

通常GC Roots是一個在current thread(當前線程)的call stack(調用棧)上的對象(例如方法參數和局部變量),或者是線程自身或者是system class loader(系統類加載器)加載的類以及native code(本地代碼)保留的活動對象。所以GC Roots是分析對象為何還存活於內存中的利器。

MAT界面功能介紹

打開經過轉換的hprof文件:
這裡寫圖片描述
可不選

Actions區域,幾種分析方法:

Histogram:列出內存中的對象,對象的個數以及大小
這裡寫圖片描述

Dominator Tree:列出最大的對象以及其依賴存活的Object (大小是以Retained Heap為標准排序的)
這裡寫圖片描述

點開每個對象,檢查內部的超大對象

Top Consumers : 通過圖形列出最大的object
這裡寫圖片描述

一般Histogram和 Dominator Tree是最常用的。

MAT分析對象的引用

Path to GC Root
在Histogram或者Domiantor Tree的某一個條目上,右鍵可以查看其GC Root Path:
這裡寫圖片描述

點擊Path To GC Roots –> with all references

這裡寫圖片描述

通過這個圖查看(內存洩漏)該內存還被誰所引用,為何還不能釋放

MAT基礎介紹來自Gracker

三、內存問題總覽

內存洩漏

非靜態內部類的靜態實例容易造成內存洩漏
非靜態內部類的存活需要依賴外部類

Activity使用靜態成員(靜態成員引用Drawable、Bitmap等大內存對象)

使用handler時的內存問題
因為Handler的非即時性,導致部分代碼不能及時釋放
可以使用Badoo開發的第三方的 WeakHandler

注冊某個對象後未反注冊
注冊廣播接收器、注冊觀察者等等

集合中對象沒清理造成的內存洩露

資源對象沒關閉造成的內存洩露
資源性對象比如(Cursor,File文件等)往往都用了一些緩沖,我們在不使用的時候,應該及時關閉它們,以便它們的緩沖及時回收內存。

一些不良代碼成內存壓力

循環以及遞歸 數據隨意申請大小 如果沒有用到不要定義全局變量

Bitmap使用不當

及時的銷毀

雖然,系統能夠確認Bitmap分配的內存最終會被銷毀,但是由於它占用的內存過多,所以很可能會超過Java堆的限制。因此,在用完Bitmap時,要及時的recycle掉。recycle並不能確定立即就會將Bitmap釋放掉,但是會給虛擬機一個暗示:“該圖片可以釋放了”。

設置一定的采樣率(二次采樣)
有時候,我們要顯示的區域很小,沒有必要將整個圖片都加載出來,而只需要記載一個縮小過的圖片,這時候可以設置一定的采樣率,那麼就可以大大減小占用的內存。

巧妙的運用軟引用(SoftRefrence)

有些時候,我們使用Bitmap後沒有保留對它的引用,因此就無法調用Recycle函數。這時候巧妙的運用軟引用,可以使Bitmap在內存快不足時得到有效的釋放
目前但凡是個圖片加載框架都會使用SoftRefrence

構造Adapter時,沒有使用緩存的 convertView

頻繁的方法中創建對象
不要在經常調用的方法中創建對象,尤其是忌諱在循環中創建對象。可以適當的使用 hashtable , vector 創建一組對象容器,然後從容器中去取那些對象,而不用每次 new 之後又丟棄。

四、 MAT內存分析

使用Dominator Tree -> Path To GC Roots –> with all references
上面MAT基礎裡面已經講

根據某種類型的對象個數來分析內存洩漏。
Actions -> Histogram
這裡寫圖片描述

上圖展示了內存中各種類型的對象個數和Shallow heap,我們看到byte[]占用Shallow heap最多,那是因為Honeycomb之後Bitmap Pixel Data的內存分配在Dalvik heap中。右鍵選中byte[]數組,選擇List Objects -> with incomingreferences,可以看到byte[]具體的對象列表:

這裡寫圖片描述

這裡寫圖片描述

我們發現第二個byte[]的Retained heap較大,內存洩漏的可能性較大,因此右鍵選中這行,Path To GC Roots -> exclude weak references,同樣可以看到上文所提到的情況,我們的Bitmap對象被leak所引用到,這裡存在著內存洩漏。

這裡寫圖片描述

講的是對象及其應用的內存大小 講的是大型對象被誰所引用

內存分析工具還有一個比較流行的內存洩漏檢測庫:LeakCannary

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved