前一段時(shí)間自己學(xué)習(xí)了下Android內(nèi)存管理相關(guān)的東西,在部門內(nèi)部做了一次技術(shù)分享。作為一個(gè)在公開(kāi)場(chǎng)合會(huì)靦腆,上不了臺(tái)面,表達(dá)能力也不行的人來(lái)說(shuō)是一個(gè)不小的進(jìn)步。一直很佩服墻內(nèi)墻外的牛人們堅(jiān)持寫博客和大家分享的精神。嗯,今天就將上次的技術(shù)分享寫在博客里面,希望對(duì)大家有幫助。
下面分為4個(gè)部分來(lái)闡述:
1.Linux vs. Windows
兩者都能將物理內(nèi)存中長(zhǎng)時(shí)間不使用的內(nèi)容轉(zhuǎn)移到磁盤空間上,再次訪問(wèn)時(shí)才加載回內(nèi)存。
Windows只在應(yīng)用程序需要的時(shí)候才會(huì)分配內(nèi)存,不能充分充分利用內(nèi)存。
Linux充分利用物理內(nèi)存空間(RAM)及其高速讀寫特性,將程序調(diào)用過(guò)程中的硬盤數(shù)據(jù)讀入內(nèi)存,提高數(shù)據(jù)訪問(wèn)性能。
Android是基于Linux系統(tǒng)的,繼承了Linux的很多優(yōu)秀特性。但是Android沒(méi)有可交換磁盤空間(swap space),所有的內(nèi)存都存在于RAM中,這使得Android系統(tǒng)釋放內(nèi)存的唯一方式就是釋放引用的對(duì)象。
2.Android進(jìn)程和內(nèi)存
Android進(jìn)程包含2種:
(1)Native進(jìn)程:采用C/C++實(shí)現(xiàn),不包含dalvik實(shí)例的進(jìn)程。/system/bin/目錄下面的文件運(yùn)行后都是以native進(jìn)程存在的。
(2)Java進(jìn)程:Android中運(yùn)行于dalvik虛擬機(jī)上的進(jìn)程。dalvik虛擬機(jī)的宿主進(jìn)程由fork()系統(tǒng)調(diào)用創(chuàng)建,所以每一個(gè)Java進(jìn)程都存在于一個(gè)Native進(jìn)程中。由于每個(gè)Java存在一個(gè)虛擬機(jī)實(shí)例,因此Java進(jìn)程內(nèi)存分配要比Native進(jìn)程復(fù)雜。
3.Android中的java程序?yàn)樯度菀譕OM?
Android對(duì)dalvik的vm heapsize作了限制,當(dāng)Java進(jìn)程申請(qǐng)的內(nèi)存超過(guò)閾值時(shí),會(huì)拋出OOM異常。
程序發(fā)生OOM并不能說(shuō)明RAM不足,只能說(shuō)明是Java heap超出了dalvik vm heapsize的閾值。
當(dāng)RAM內(nèi)存不足時(shí),memory killer會(huì)殺掉低優(yōu)先級(jí)進(jìn)程,保證高優(yōu)先級(jí)進(jìn)程有更多內(nèi)存。
Google之所以這樣設(shè)計(jì),處于以下考慮:
為了讓比較多的進(jìn)程同時(shí)常駐內(nèi)存,這樣程序啟動(dòng)時(shí)不需要每次都加載到內(nèi)存,能夠給用戶更快的響應(yīng)。通過(guò)限制每個(gè)應(yīng)用程序的內(nèi)存,使得Android系統(tǒng)內(nèi)存中同時(shí)常駐多個(gè)進(jìn)程。
4.如何繞過(guò)dalvik vm heapsize的限制
(1)創(chuàng)建子進(jìn)程
(2)使用JNI在native heap上申請(qǐng)內(nèi)存(推薦)
(3)使用RAM中的顯存空間
1.Use services sparingly
Service
,當(dāng)后臺(tái)任務(wù)運(yùn)行完成時(shí)及時(shí)關(guān)閉Service
IntentService
取代Service
,當(dāng)后臺(tái)任務(wù)完成時(shí)自動(dòng)結(jié)束服務(wù)自身原因:Service啟動(dòng)時(shí),Service所在進(jìn)程會(huì)保持運(yùn)行狀態(tài),Service所占用的內(nèi)存不會(huì)釋放,使得進(jìn)程占用資源過(guò)多,因此系統(tǒng)LRU cache中能同時(shí)保持的進(jìn)程數(shù)減少,應(yīng)用程序的切換變得更低效。由于沒(méi)有足夠的進(jìn)程來(lái)處理系統(tǒng)中的Service,也會(huì)導(dǎo)致系統(tǒng)穩(wěn)定性變差。
2.當(dāng)UI不可見(jiàn)或內(nèi)存緊張時(shí),釋放內(nèi)存:
在Activity
的回調(diào)方法onTrimMemory(int level)
中根據(jù)level的不同釋放內(nèi)存。
進(jìn)程不在緩存中:
TRIM_MEMORY_RUNNING_MODERATE
應(yīng)用程序正在運(yùn)行,并且處于非killable狀態(tài),此時(shí)設(shè)備內(nèi)存低(low),系統(tǒng)主動(dòng)殺LRU緩存中的進(jìn)程TRIM_MEMORY_RUNNING_LOW
應(yīng)用程序正在運(yùn)行,并且處于非killable狀態(tài),此時(shí)設(shè)備內(nèi)存很低(much lower),需要釋放沒(méi)用的資源TRIM_MEMORY_RUNNING_CRITICAL
應(yīng)用程序正在運(yùn)行,但是系統(tǒng)已殺死LRU緩存中的大部分進(jìn)程,此時(shí)需要釋放所有不至關(guān)重要的資源。如果系統(tǒng)不能回收足夠的內(nèi)存,就會(huì)清掉LRU中所有的進(jìn)程以及服務(wù)進(jìn)程。 進(jìn)程在LRU緩存中:
TRIM_MEMORY_BACKGROUND
系統(tǒng)低內(nèi)存下運(yùn)行,程序進(jìn)程位于LRU緩存列表的開(kāi)頭位置。雖然程序進(jìn)程被kill的概率不大,但是系統(tǒng)可能正在殺LRU中的進(jìn)程。你需要釋放容易恢復(fù)的資源以便程序進(jìn)程還在LRU list中,當(dāng)從其他App返回時(shí),能快速恢復(fù)現(xiàn)場(chǎng)。TRIM_MEMORY_MODERATE
系統(tǒng)低內(nèi)存下運(yùn)行,程序進(jìn)程位于LRU緩存列表的中間位置。你的進(jìn)程被殺掉的可能性變大。TRIM_MEMORY_COMPLETE
系統(tǒng)低內(nèi)存下運(yùn)行,程序進(jìn)程最先容易被系統(tǒng)殺死。你需要釋放所有對(duì)于恢復(fù)程序狀態(tài)不至關(guān)重要的資源。 API14開(kāi)始有onTrimMemory()
回調(diào);API 14以下使用的是onLowMemory()
,等價(jià)于TRIM_MEMORY_COMPLETE
3.恰當(dāng)使用Bitmap
加載bitmap時(shí),盡量保證bitmap分辨率和屏幕分辨率匹配,對(duì)于大分辨率的bitmap需要進(jìn)行壓縮處理。
4.使用SparseArray
,SparseBooleanArray
和LongSparseArray
等優(yōu)化的數(shù)據(jù)容器代替HashMap
5.使用static const代替enum
6.非必要情況下,少用抽象
7.對(duì)于序列化數(shù)據(jù),使用nano protobuf
8.盡量少使用依賴注入框架
9.謹(jǐn)慎使用第三方庫(kù)
10.使用ProGuard去除不必要的代碼
11.apk打包簽名時(shí),使用zipalign工具對(duì)齊
12.使用多進(jìn)程
一般情況下,大多數(shù)應(yīng)用程序都不需要使用多進(jìn)程。只有對(duì)于需要在后臺(tái)和前臺(tái)同時(shí)運(yùn)行,并且前后臺(tái)單獨(dú)管理的程序才涉及到多進(jìn)程(如音樂(lè)播放器)。
使用多進(jìn)程方法:
<service android:name=".PlaybackService" android:process=":background" />
一個(gè)空進(jìn)程占用大概1.4MB內(nèi)存,如果在該進(jìn)程中操作UI,內(nèi)存占用將是原來(lái)的好幾倍。因此如果使用多進(jìn)程,一般一個(gè)進(jìn)程用于UI,其他進(jìn)程不要有任何UI相關(guān)操作。
1.分析Logcat信息
D/dalvikvm:<GC_Reason><Amount_freed>,<Heap_stats>,<External_memory_stats>,<Pause_time> 參數(shù)解釋:
2.利用Eclipse的DDMS視圖自帶的內(nèi)存分析工具
3.使用adb命令行
adb shell dumpsys meminfo <package_name>
4.使用MAT內(nèi)存分析工具
下載eclipse插件memory analyze tool
操作DDMS -> Dump HPROF file -> save,分析Histigram view和Dominator tree內(nèi)容。
重要概念:shallow heap和retained heap
1.GC原理
GC會(huì)選擇一些還存活的對(duì)象作為內(nèi)存遍歷的根節(jié)點(diǎn)GC Roots(如thread stack中的變量,JNI中的全局變量,zygote中的class loader對(duì)象等),對(duì)heap進(jìn)行遍歷,沒(méi)有被直接或間接遍歷到的引用會(huì)被GC回收,能被遍歷到的不能被回收。
內(nèi)存泄露:某些不再使用的對(duì)象被GC Roots引用,導(dǎo)致不能回收,使實(shí)際可使用內(nèi)存變小。
2.引起內(nèi)存泄露的因素
(1)長(zhǎng)時(shí)間保持對(duì)Activity
,Context
,View
,Drawable
和其他對(duì)象的引用
(2)非靜態(tài)內(nèi)部類
(3)持有對(duì)象的時(shí)間超出需要的時(shí)間
3.常見(jiàn)的內(nèi)存泄露
(1)非靜態(tài)內(nèi)部類的靜態(tài)實(shí)例
(2)Activity
使用靜態(tài)成員
(3)Handler
、HandlerThread
使用時(shí)的問(wèn)題
(4)register某個(gè)對(duì)象后缺少對(duì)應(yīng)的unregister操作
(5)集合對(duì)象未清理,資源對(duì)象未關(guān)閉
(6)Dialog或PopupWindow未關(guān)閉引起的window leak
(7)不良代碼造成的壓力。如Bitmap使用不當(dāng);構(gòu)造adapter時(shí),沒(méi)有使用緩存的convertView;在循環(huán)方法中創(chuàng)建對(duì)象。
4.改進(jìn)建議
WeakReference
聯(lián)系客服