博客內(nèi)容來自菜鳥教程 http://www.runoob.com/w3cnote/android-oom.html
1. 什么是 OOM
E/dalvikvm-heap(13316): Out of memory on a 10485776-byte allocation.
E/AndroidRuntime(13316): java.lang.OutOfMemoryError
這幾句的意思是棠赛,我們程序申請需要10485776byte太大了墨榄,虛擬機(jī)無法滿足我們僻澎,程序自然而然就掛了庆猫。
通常發(fā)生 OOM 的情形:
????????APP 開發(fā)中用到大量的圖片和很大的圖片停局。
通俗講就是:
????????當(dāng)我們的APP需要申請一塊內(nèi)存來裝圖片的時候稚茅,系統(tǒng)覺得我們的APP所使用的內(nèi)存已經(jīng)夠多了。即使它有1G的空余內(nèi)存
袱院,它不同意給我的APP更多的內(nèi)存里屎慢,然后即使系統(tǒng)馬上拋出OOM錯誤瞭稼,而程序沒有捕捉該錯誤,故彈框崩潰了腻惠。
2.為什么會有OOM环肘?
2.1 Android的APP內(nèi)存組成:
APP內(nèi)存由 ** dalvik內(nèi)存** 和 ** native內(nèi)存 2部分組成,
** dalvik : ** java堆集灌,創(chuàng)建的對象就是就是在這里分配的悔雹,
** native : 是通過c/c++方式申請的內(nèi)存,Bitmap就是以這種方式分配的欣喧。
(android3.0以后腌零,系統(tǒng)都默認(rèn)通過dalvik分配的,native作為堆來管理)
這2部分加起來不能超過android對單個進(jìn)程唆阿,虛擬機(jī)的內(nèi)存限制益涧。
每個手機(jī)的內(nèi)存限制大小是多少?
ActivityManager activityManager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)
activityManager.getMemoryClass();
以上方法會返回以M為單位的數(shù)字酷鸦,不同的系統(tǒng)平臺或設(shè)備上的值都不太一樣 .
而對于head堆的大小限制,可以查看/system/build.prop文件牙咏。
dalvik.vm.heapstartsize = 5m
dalvik.vm.heapgrowthlimit = 48m
dalvik.vm.heapsize = 256m
注: heapsize參數(shù)表示單個進(jìn)程heap可用的最大內(nèi)存臼隔,但如果存在以下參數(shù)"dalvik.vm.headgrowthlimit =48m"表示單個進(jìn)程heap內(nèi)存被限定在48m,即程序運(yùn)行過程實際只能使用48m內(nèi)存 .
2.2 為什么android系統(tǒng)設(shè)定APP的內(nèi)存限制妄壶?
- 1 要使開發(fā)者內(nèi)存使用更為合理摔握。 限制每個應(yīng)用的可用內(nèi)存上限,可以放置某些應(yīng)用程序惡意或者無意的使用過多的內(nèi)存丁寄。而導(dǎo)致其它應(yīng)用無法正常運(yùn)行氨淌。Android是有多進(jìn)程的,如果一個進(jìn)程(就是一個應(yīng)用)耗費(fèi)過多的內(nèi)存伊磺,其他應(yīng)用就無法運(yùn)行了盛正。因為有了限制,使得開發(fā)者必須好好利用有限資源屑埋,優(yōu)化資源的使用豪筝。
- 2 屏幕顯示內(nèi)容有限,內(nèi)存足夠即可摘能。 即使有萬千圖片千萬數(shù)據(jù)需要使用到续崖,但在特定時刻需要展示給用戶看的總是有限的,因為屏幕顯示就那么大团搞,上面可以放的信息就是很有限的严望。大部分信息都是處于準(zhǔn)備顯示狀態(tài),所以沒必要給予太多heap內(nèi)存逻恐。也就是說出現(xiàn) OOM現(xiàn)象像吻,絕大部分原因是我們的程序設(shè)計上有問題峻黍,需要優(yōu)化 。優(yōu)化方法很多萧豆,比如通過時間換空間奸披,不停的加載要用的的圖片,不停的回收不用的圖片涮雷,把大圖片解析成適合手機(jī)屏幕大小的圖片等阵面。
- 3 多APP多個虛擬機(jī)davlik的限制需要。 android上的app使用獨(dú)立虛擬機(jī)洪鸭,每開一個應(yīng)用就會打開至少一個獨(dú)立的虛擬機(jī)样刷。這樣可以避免虛擬機(jī)崩潰導(dǎo)致整個系統(tǒng)崩潰,同時代價就是需要浪費(fèi)更多的內(nèi)存览爵。這樣設(shè)計保證了android的穩(wěn)定性置鼻。
2.3 不是GC自動回收資源么,為什么還會OOM?
Android的gc會按照特定的算法回收程序不用的內(nèi)存資源蜓竹,避免app的內(nèi)存申請越積越多箕母,但是gc一般回收的資源是那些無主的對象內(nèi)存或者軟引用的資源,或者更軟引用的引用資源俱济。
比如:
Bitmap bt = BitmapFactory.decodeResource( this .getResources(), R.drawable.splash); //此時的圖片資源是強(qiáng)引用嘶是,是有主的資源。
bt = null ; //此時這個圖片資源就是無主的了蛛碌。gc心情號的時候就會去回收它聂喇。
SoftReference<Bitmap> softRef = new SoftReference<Bitmap>(bt);
bt = null ;
其他代碼...
當(dāng)程序申請很多內(nèi)存資源時,gc有可能會釋放softref引用的這個圖片內(nèi)存蔚携。bt=softRef.get()希太,此時可能得到的是null,需要重新加載圖片酝蜒。
當(dāng)然這也說明了用軟引用圖片資源的好處誊辉,就是gc會自動根據(jù)需要釋放資源,一定程度上避免OOM亡脑。
TIPS:編程要養(yǎng)成習(xí)慣芥映,不用的對象設(shè)置為null。其實更好的是远豺,不用的圖片直接recycle奈偏。因為通過設(shè)置null讓gc來回收,有時候還是會來不及躯护。
2.4 怎么查看APP內(nèi)存分配情況?
- 1 ** 通過DDMS中的heap選項卡監(jiān)視內(nèi)存情況:**
Heap視圖中部有一個叫做data object惊来, 即數(shù)據(jù)對象,也就是我們的程序中大量存在的類類型的對象棺滞。
在data object一行中有一列是"Total Size", 其值就是當(dāng)前進(jìn)程中所有Java數(shù)據(jù)對象的內(nèi)存總量裁蚁。如果代碼中存在沒有釋放對象引用的情況矢渊,則data object的"Total Size"值在每次gc后不會有明顯的回落。隨著操作次數(shù)的增加"Total Size"的值會越來越大枉证。直到到達(dá)一個上限 后導(dǎo)致進(jìn)程被kill掉矮男。 - 2 在App里面我們可以通過totalMemory與freeMemory:
Runtime.getRuntime().freeMemory()
RUntime.getRuntime().totalMemory()
- 3
adb shell dumpsys meminfo com.android.demo
3. 常見避免OOM的幾個注意點(diǎn):
3.1 適當(dāng)調(diào)整圖像大小 。
因為手機(jī)屏幕尺寸有限室谚,分配給圖像的顯示區(qū)域有限毡鉴,尤其對于超大圖片,加載自網(wǎng)絡(luò)或者sd卡秒赤,圖片文件提及達(dá)到幾M或者十幾個M的:加載到內(nèi)存前猪瞬,先算出該bitmap的大小,然后通過適當(dāng)調(diào)節(jié)采樣率使得加載的圖片剛好入篮,或稍大尺寸在手機(jī)屏幕上顯示就滿意了:
BimtapFactory.Option opts = new BitampFactory.Option();
opts.inJustDecodeBounds = true ;
opts.inSampleSize=computeSample(opts, minSideLength, maxNumOfPixels); // Android 提供了一種動態(tài)計算的方法 computeSampleSize
opts.inJustDecodeBounds = false ;
try {
return BitmapFactory.decodeFile(imageFile, opts);
} catch (OutOfMemoryError err){
}
3.2 圖像緩存 陈瘦。
在listview或Gallery等控件中一次性加載大量圖片時,只加載屏幕顯示的資源潮售,尚未顯示的不加載痊项,移出屏幕的資源及時釋放,采用強(qiáng)引用+軟引用2級緩存酥诽,提高加載性能鞍泉。緩存圖像到內(nèi)存,采用軟引用緩存到內(nèi)存盆均,而不是在每次使用的時候都從新加載到內(nèi)存塞弊。
3.3 采用低內(nèi)存占用量的編碼方式 漱逸。
比如Bitmap.Config.ARGB_4444比Bitmap.Config.ARGB_8888更省內(nèi)存泪姨。
3.4 及時回收圖像 。
如果引用了大量的Bitmap對象饰抒,而應(yīng)用又不需要同時顯示所有圖片肮砾。可以將暫時不用到的Bitmap對象及時回收掉袋坑。對于一些明確直到圖片使用情況的場景可以主動recycle回收
App的啟動splash畫面上的圖片資源仗处,使用完就recycle。對于幀動畫枣宫,可以加載一張婆誓,畫一張,釋放一張也颤。
3.5 不要在循環(huán)中創(chuàng)建過多的本地變量 洋幻。
慎用static,用static來修飾成員變量時翅娶,該變量就屬于該類文留,而不是該類實例好唯,它的生命周期是很長的。如果用它來引用一些內(nèi)存占用太多的實例燥翅,這時候就要謹(jǐn)慎對待了骑篙。
3.6 自定義堆內(nèi)存分配大小 。
優(yōu)化Dalvik虛擬機(jī)的堆內(nèi)存分配森书。
public class ClassName{
private static Context mContext;
// 省略
}
4. App使用圖片時避免OOM的幾種方式:
4.1 直接null或recycle
對于app里使用的大量圖片靶端,采用方式:使用時加載,不顯示時直接置null或recycle拄氯。
這樣處理是個好習(xí)慣躲查,基本可以杜絕OOM,但是缺憾是代碼多了译柏,可能會忘記某些資源recycle镣煮。
而有些情況下會出現(xiàn)特定圖片反復(fù)加載,釋放鄙麦,再加載等典唇,低效率的事情。
4.2 簡單通過SoftReference引用方式管理圖片資源
建個SoftReference的hashmap
使用圖片時先查詢這個hashmap是否有softreference
胯府, softreference里的圖片是否為空介衔,
如果為空就加載圖片到softreference并加入hashmap。
無需再代碼里顯式的處理圖片的回收與釋放骂因,gc會自動處理資源的釋放炎咖。
這種方式處理起來簡單實用,能一定程度上避免前一種方法反復(fù)加載釋放的低效率寒波。但還不夠優(yōu)化乘盼。
4.3 強(qiáng)引用+軟引用二級緩存
Android示范程序ImageDownloader.java, 使用了一個二級緩存機(jī)制。就是有一個數(shù)據(jù)結(jié)構(gòu)直接持有解碼成功的Bitmap對象引用俄烁,同時使用一個二級緩存數(shù)據(jù)結(jié)構(gòu)保持淘汰的Bitmap的softreference對象绸栅,由于softreference對象的特殊性,系統(tǒng)會在需要內(nèi)存的時候首先將softreference持有的對象釋放掉页屠,也就是說當(dāng)jvm發(fā)現(xiàn)可用的內(nèi)存較少需要出發(fā)gc的時候粹胯,二級緩存中的bitmap對象將被回收,而持有一級緩存的bitmap對象用于顯示辰企。
其實這個解決方案最為關(guān)鍵的一點(diǎn)是使用了一個比較合適的數(shù)據(jù)結(jié)構(gòu)风纠,那就是LinkedHashMap
類型來進(jìn)行一級緩存Bitmap的容器。由于LinkeHashMap的特殊性牢贸,我們可以控制其內(nèi)存存儲對象的個數(shù)并且將不再使用的對象從容器中移除竹观,放到softreference二級緩存里,我們可以在一級緩存中一直保存最近被訪問到的bitmap對象十减,而已經(jīng)被訪問過的圖片在LinkedHashMap的容量超過我們預(yù)設(shè)值時將會把容器中存在的時間最長的對象移除栈幸,這個時候我么可以將被移除的LinkedHashMap中的放到二級緩存容器愤估,而二級緩存中的對象管理就交給系統(tǒng)來做了,當(dāng)系統(tǒng)需要gc時就會首先回收二級緩存容器的Bitmap對象了速址。
** 在獲取圖片對象時候先從一級緩存容器中查找玩焰,如果有對應(yīng)對象并可用直接返回,如果沒有的話從二級緩存中查找對應(yīng)的SoftReference, 判斷SoftReference對象持有的Bitmap是否可用芍锚,可用直接返回昔园,否則返回空。如果二級緩存都找不到圖片并炮,那就直接加載圖片資源默刚。
**
4. LruCache + sd的緩存方式
5. 兩種容易OOM的場景建議:
5.1 網(wǎng)絡(luò)下載大量圖片
比如微博客戶端: 多線程異步網(wǎng)絡(luò),小兔直接用LRUCache+SoftRef+Sd逃魄,大圖按需下載:
5.2 對于需要加載非常多條目信息的listview荤西,gridview等的情況
在adapter的getView函數(shù)里有個convertView參數(shù),告知你是否有可重用的view對象伍俘。 如果不使用convertView的話邪锌,每次調(diào)用getView時每次都會重新創(chuàng)建view,這樣之前的view可能還沒銷毀癌瘾,加之不斷的新建view勢必會造成內(nèi)存劇增觅丰,從而導(dǎo)致OOM。另外在重用convertView時妨退,里面原有的圖片等資源就會變成無主的了妇萄。
這里Google官方推薦使用:"convertview+靜態(tài)類viewholder"
官方給出解釋是:
a 重用緩存convertView傳遞給getView()方法來避免填充不必要的視圖。
b 使用ViewHolder模式來避免沒有必要的調(diào)用findViewById咬荷;因為太多的findViewById也會影響性能冠句。
附ViewHolder類的作用:
ViewHolder模式通過在getView方法返回的視圖的標(biāo)簽(tag)中存儲一個數(shù)據(jù)結(jié)構(gòu)。這個數(shù)據(jù)結(jié)構(gòu)包含了指向我們要綁定數(shù)據(jù)的視圖的引用萍丐,從而避免每次調(diào)用getView()的時候調(diào)用findViewById();
6 申請超過內(nèi)存限制的內(nèi)存分配方式:
** 6.1 從Native C分配內(nèi)存轩端。**
使用NDK(本地開發(fā)工具包)和JNI, 它可能從C級(如malloc/free或新建/刪除)分配內(nèi)存放典,這樣的分配是不計入24MB的限制逝变。這是真的,從本機(jī)代碼分配內(nèi)存是為了java方便奋构,但它可以被用來存儲在ram的數(shù)據(jù)(即使圖片數(shù)據(jù))的一些打擊呢壳影。
** 6.2 使用OpenGL的紋理。**
紋理內(nèi)存不計入限制弥臼,要查看你的應(yīng)用程序確實分配了多少內(nèi)存可以使用android.os.Debug.getNativeHeapAllocatedSize(), 可以使用上面介紹的兩種技術(shù)的Nexus之一宴咧,我可以輕松地為一個單一的前臺進(jìn)程分配300MB-10倍以上的默認(rèn)24MB 的限制,從上面看來使用native代碼分配內(nèi)存是不在24MB的限制內(nèi)的(開放的GL的質(zhì)地也是使用native代碼分配內(nèi)存)径缅。
但是掺栅,這兩個方法的風(fēng)險就是烙肺,本地堆分配內(nèi)存超過系統(tǒng)可用內(nèi)存限制的話,通常都是直接崩潰氧卧。