1.什么是OOM?
03-21 21:05:28 . 771: e/dalvikvm-heap(13316): out of memory on a 10485776-byte allocation .
03-21 21:05:28.7793360 e/Android運(yùn)行時(13316): Java . lang . out of memory error
這幾句話的意思是,我們的程序申請需要10485776byte太大,虛擬機(jī)無法滿足我們,可恥的shutdown自殺了。這種現(xiàn)象通常出現(xiàn)在使用大量圖片或大圖片的APP開發(fā)中。一般來說,當(dāng)APP需要申請一塊內(nèi)存來安裝照片時,系統(tǒng)認(rèn)為APP已經(jīng)使用了足夠的內(nèi)存。(大衛(wèi)亞設(shè),Northern Exposure(美國電視新聞),即使有1G的空閑內(nèi)存,我也不同意為APP提供更多內(nèi)存。然后,系統(tǒng)會立即產(chǎn)生OOM錯誤,如果程序沒有捕獲錯誤,炸彈箱就會崩潰。
2.為什么有OOM?
如果請求的內(nèi)存資源超過此限制,則會出現(xiàn)OOM錯誤,因?yàn)锳ndroid系統(tǒng)中的每個app的每個進(jìn)程或每個虛擬機(jī)都有最大內(nèi)存限制。與整個設(shè)備的其馀內(nèi)存沒有太大關(guān)系。例如,以前Android系統(tǒng)的虛擬機(jī)最多有16M的內(nèi)存,當(dāng)app啟動后,虛擬機(jī)將繼續(xù)請求內(nèi)存資源裝載圖片,如果超過內(nèi)存限制,將出現(xiàn)OOM。Android系統(tǒng)的APP內(nèi)存限制是如何確定的?
2.1 Android的APP內(nèi)存組件:
APP內(nèi)存由Java堆dalvik內(nèi)存和本地內(nèi)存兩部分組成。在此處創(chuàng)建的對象將分配給此處,native是通過c/c請求的內(nèi)存。位圖是以這種方式分配的。Android3.0之后的系統(tǒng)默認(rèn)通過dalvik分配,native作為堆進(jìn)行管理。這兩部分加起來不能超過Android對單個進(jìn)程、虛擬機(jī)的內(nèi)存限制。
每個手機(jī)的內(nèi)存限制大小是多少?
activity manager activity manager=(activity manager)
AC();
上述方法返回以M為單位的數(shù)字,每個系統(tǒng)平臺或設(shè)備的值都不同,如HTC基本24M、galaxy 36M、EMULA24M等。我的座右銘xt681是42米。3
以上是虛擬機(jī)的最大內(nèi)存資源。
對于Head堆大小限制,可以查看/system文件。
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。換句話說,程序執(zhí)行過程實(shí)際上只能使用48m內(nèi)存。
2.2 Android系統(tǒng)為什么設(shè)置APP的內(nèi)存限制?
1開發(fā)人員內(nèi)存使用應(yīng)更加合理。通過限制每個應(yīng)用程序可用的最大內(nèi)存限制,某些應(yīng)用程序可能會惡意或無意地使用太多內(nèi)存。這導(dǎo)致其他應(yīng)用程序無法正常工作。Android是一個多進(jìn)程,因此,如果一個進(jìn)程(即應(yīng)用程序)使用了太多內(nèi)存,則無法運(yùn)行其他應(yīng)用程序。因?yàn)橛芯窒扌?,開發(fā)人員必須善用有限的資源,優(yōu)化資源使用。
2屏幕顯示內(nèi)容有限,內(nèi)存足夠就可以了。需要使用數(shù)千萬張照片上千萬個數(shù)據(jù),但在特定時間點(diǎn)需要向用戶顯示的東西總是有限的。因?yàn)槠聊伙@示太大,可以放在上面的信息有限。大部分信息都已準(zhǔn)備好顯示,因此不需要給heap內(nèi)存太多。也就是說,出現(xiàn)OOM現(xiàn)象,大多數(shù)原因是我們的程序設(shè)計有問題,需要優(yōu)化。優(yōu)化方法有很多。例如,通過時間改變空間,繼續(xù)加載要使用的照片,繼續(xù)回收不使用的照片,將大照片分析為適合手機(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自動回收資源。為什么app的未使用資源不被回收利用?
Android的GC根據(jù)特定算法回收程序未使用的內(nèi)存資源,防止app的內(nèi)存應(yīng)用程序大致堆積,但GC通?;厥盏馁Y源是無主的對象內(nèi)存、軟參考資源或軟參考資源。例如:
Bitmap Bt=bi (this.getresources()、r . drawable . splash);//此時照片資源是強(qiáng)參考,是主人所在的資源。
Bt=null//此時,這幅畫資源是無主的。Gc心情號碼去回收。
softreferencebitmap soft ref=new softreferencebitmap(Bt);
Bt=null
其他代碼……。
當(dāng)程序請求大量內(nèi)存資源時,GC可以釋放softref引用的此圖片內(nèi)存。Bt=(),此時可以
能得到的是null,需要重新加載圖片。當(dāng)然這也說明了用軟引用圖片資源的好處,就是gc會自動根據(jù)需要釋放資源,一定程度上避免OOM。
TIPS:編程要養(yǎng)成習(xí)慣,不用的對象設(shè)置為null。其實(shí)更好的是,不用的圖片直接recycle。因?yàn)橥ㄟ^設(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:
Run().freeMemory()
RUn().totalMemory()
3 adb shell dumpsys meminfo com.android.demo
3. 常見避免OOM的幾個注意點(diǎn):
3.1 適當(dāng)調(diào)整圖像大小 。因?yàn)槭謾C(jī)屏幕尺寸有限,分配給圖像的顯示區(qū)域有限,尤其對于超大圖片,加載自網(wǎng)絡(luò)或者sd卡,圖片文件提及達(dá)到幾M或者十幾個M的:
加載到內(nèi)存前,先算出該bitmap的大小,然后通過適當(dāng)調(diào)節(jié)采樣率使得加載的圖片剛好,或稍大捷克在手機(jī)屏幕上顯示就滿意了:
Bim opts = new Bi();
o = true ;
o(opts, minSideLength, maxNumOfPixels); // Android 提供了一種動態(tài)計算的方法 computeSampleSize
o = false ;
try {
return Bi(imageFile, opts);
} catch (OutOfMemoryError err){
}
3.2 圖像緩存 。在listview或Gallery等控件中一次性加載大量圖片時,只加載屏幕顯示的資源,尚未顯示的不加載,移出屏幕的資源及時釋放,采用強(qiáng)引用+軟引用2級緩存,提高加載性能。緩存圖像到內(nèi)存,采用軟引用緩存到內(nèi)存,而不是在每次使用的時候都從新加載到內(nèi)存。
3.3 采用低內(nèi)存占用量的編碼方式 。比如Bi比Bi更省內(nèi)存。
3.4 及時回收圖像 。如果引用了大量的Bitmap對象,而應(yīng)用又不需要同時顯示所有圖片??梢詫簳r不用到的Bitmap對象及時回收掉。對于一些明確直到圖片使用情況的場景可以主動recycle回收
App的啟動splash畫面上的圖片資源,使用完就recycle。對于幀動畫,可以加載一張,畫一張,釋放一張。
3.5 不要在循環(huán)中創(chuàng)建過多的本地變量 。慎用static,用static來修飾成員變量時,該變量就屬于該類,而不是該類實(shí)例,它的生命周期是很長的。如果用它來引用一些內(nèi)存占用太多的實(shí)例,這時候就要謹(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會自動處理資源的釋放。
這種方式處理起來簡單實(shí)用,能一定程度上避免前一種方法反復(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)vm發(fā)現(xiàn)可用的內(nèi)存較少需要出發(fā)gc的時候,二級緩存中的bitmap對象將被回收,而持有一級緩存的bitmap對象用于顯示。
其實(shí)這個解決方案最為關(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;因?yàn)樘嗟膄indViewById也會影響性能。
附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)用程序確實(shí)分配了多少內(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)存限制的話,通常都是直接崩潰。
1.《【cfoutofmemory怎么解決】Android應(yīng)用OOM問題分析及解決方案》援引自互聯(lián)網(wǎng),旨在傳遞更多網(wǎng)絡(luò)信息知識,僅代表作者本人觀點(diǎn),與本網(wǎng)站無關(guān),侵刪請聯(lián)系頁腳下方聯(lián)系方式。
2.《【cfoutofmemory怎么解決】Android應(yīng)用OOM問題分析及解決方案》僅供讀者參考,本網(wǎng)站未對該內(nèi)容進(jìn)行證實(shí),對其原創(chuàng)性、真實(shí)性、完整性、及時性不作任何保證。
3.文章轉(zhuǎn)載時請保留本站內(nèi)容來源地址,http://f99ss.com/gl/2561848.html