編輯:關於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常駐其中。
創建子進程
創建一個新的進程,那麼我們就可以把一些對象分配到新進程的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申請的內存就是顯存。
進程的地址空間
在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
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:.....由於用到的太少,後面的就不再詳述
內存使用率低,使用率穩定(波動小)
沒有正在使用的對象,要能夠被GC回收
(避免內存洩漏)
不再使用的內存對象、或著大型內存,使用結束(虛引用)馬上回收
(finalize()方法進行清理
,通過 java.lang.ref.PhantomReference實現)
我們進行內存分析具體分析什麼呢?
1. 大型對象
2. 不使用的未能被釋放的對象(內存洩漏)
而谷歌目前提供的內存分析工具只能從宏觀上進行內存分析,無法針對某個對象進行分析
這裡我們這裡需要使用強大的第三方內存分析工具MAT(Memory Analyzer Tool)針對具體內存進行分析
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 之後又丟棄。
使用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
本文實例為大家分享了Android實現手機自動獲取短信驗證碼功能,供大家參考,具體內容如下1、短信監聽廣播2、讀取短信內容3、截取短信內容【可以 reg截取】4、填寫至相
照例先上效果圖通過該例子,你能學到什麼: 對Paint 深入理解,畫繪制餅圖,矩形,文字等 &nbs
1.一些BB上節我們把妹子圖片的數據來源從本地改成了解析Gank提供的接口數據,我們本節想對這個圖片加載類進行優化,比如加上顯示本地圖片的,另外還有一點就是緩存,我們現在
1、背景介紹在開發應用過程中經常會遇到顯示一些不同的字體風格的信息猶如默認的LockScreen上面的時間和充電信息。對於類似的情況,可能第一反應就是用不同的多個Text