OOM篇之二---OOM詳解

博客內(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)存限制的話,通常都是直接崩潰氧卧。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末桃笙,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子沙绝,更是在濱河造成了極大的恐慌搏明,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件闪檬,死亡現(xiàn)場離奇詭異星著,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)粗悯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進(jìn)店門虚循,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人样傍,你說我怎么就攤上這事邮丰。” “怎么了铭乾?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵剪廉,是天一觀的道長。 經(jīng)常有香客問我炕檩,道長斗蒋,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任笛质,我火速辦了婚禮泉沾,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘妇押。我一直安慰自己跷究,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布敲霍。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪艘儒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天杨赤,我揣著相機(jī)與錄音疾牲,去河邊找鬼阳柔。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播恕沫,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼迄委,長吁一口氣:“原來是場噩夢啊……” “哼死讹!你這毒婦竟也來了虏两?” 一聲冷哼從身側(cè)響起旁瘫,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤惠况,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后宁仔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體稠屠,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年翎苫,在試婚紗的時候發(fā)現(xiàn)自己被綠了权埠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡煎谍,死狀恐怖攘蔽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情呐粘,我是刑警寧澤秩彤,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站事哭,受9級特大地震影響漫雷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鳍咱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一降盹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧谤辜,春花似錦蓄坏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至脯倚,卻和暖如春渔彰,著一層夾襖步出監(jiān)牢的瞬間嵌屎,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人播急。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像尼夺,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子炒瘸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評論 2 350

推薦閱讀更多精彩內(nèi)容