Android中的OutOfMemoryError

OOM 的產(chǎn)生

在使用C或C++語(yǔ)言時(shí)峻村,我們可操作的內(nèi)存空間就是整個(gè)設(shè)備的物理內(nèi)存超歌,程序員需要自己聲明內(nèi)存空間专普,也需要自己在恰當(dāng)?shù)臅r(shí)機(jī)釋放掉內(nèi)存,一旦出錯(cuò)就會(huì)造成內(nèi)存泄漏码耐。而Java語(yǔ)言為了解決這個(gè)問(wèn)題追迟,在操作系統(tǒng)之上創(chuàng)造了一個(gè)Java虛擬機(jī)(JVM),讓Java語(yǔ)言編譯后的字節(jié)碼運(yùn)行在此虛擬機(jī)之上伐坏。啟動(dòng)一個(gè)Java應(yīng)用怔匣,會(huì)首先啟動(dòng)JVM握联,JVM 會(huì)向操作系統(tǒng)申請(qǐng)所需內(nèi)存桦沉,然后把內(nèi)存分成為棧內(nèi)存和堆內(nèi)存每瞒。堆內(nèi)存用以存放對(duì)象實(shí)例,并可被Java回收機(jī)制回收纯露,一旦剩余堆內(nèi)存空間不夠申請(qǐng)新對(duì)象時(shí)就會(huì)產(chǎn)生OutOfMemoryError異常剿骨。

Android內(nèi)存管理

Android的Dalvik虛擬機(jī)(DVM)是參考JVM做出來(lái)的,所以大同小異埠褪。最主要的兩個(gè)區(qū)別是:一浓利,DVM 基于寄存器,而JVM基于棧來(lái)進(jìn)行局部變量的操作钞速,當(dāng)然在性能上DVM會(huì)更快贷掖;二,在DVM上運(yùn)行的是被進(jìn)一步處理的JAVA字節(jié)碼渴语,后綴為.dex苹威,.dex 是把Java應(yīng)用中所有的.class文件合并而成,縮減了包的體積驾凶。Android中的 DVM 如 JVM 一樣對(duì)每個(gè)應(yīng)用可使用的最大內(nèi)存空間做了限制牙甫,每臺(tái)設(shè)備出廠之前廠家就對(duì)單個(gè) DVM 實(shí)例可使用的最大內(nèi)存進(jìn)行了限定。Android屆的第一款手機(jī)HTC G1的大小為16M调违。這些信息儲(chǔ)存在手機(jī)中 /system/build.prop配置文件中窟哺,如下我這是我使用的華為6p plus手機(jī)的相關(guān)信息

adb shell
shell@hwPE:/ $ cat /system/build.prop
...

dalvik.vm.heapstartsize=8m
dalvik.vm.heapgrowthlimit=192m
dalvik.vm.heapsize=512m
dalvik.vm.heaptargetutilization=0.75
dalvik.vm.heapminfree=512k
dalvik.vm.heapmaxfree=8m
...

dalvik.vm.heapstartsize為一個(gè)應(yīng)用初始分配的堆大小,越大意味著應(yīng)用第一次啟動(dòng)時(shí)越流暢技肩,但也意味著內(nèi)存耗用越快且轨。

dalvik.vm.heapgrowthlimit 這就是所謂的單個(gè)應(yīng)用可使用的最大內(nèi)存堆大小。

dalvik.vm.heapsize 此項(xiàng)表示應(yīng)用在manifest中配置android:largeHeap="true"時(shí)可使用的最大內(nèi)存堆大小亩鬼。

獲取內(nèi)存配置

有些小伙伴可能使用過(guò)以下方法來(lái)獲得內(nèi)存信息殖告,但可能就和最初的我一樣,不知道這些獲得的數(shù)據(jù)到底是啥意思雳锋。下面我們就來(lái)說(shuō)說(shuō)每個(gè)方法所獲得的數(shù)據(jù)的意義和特點(diǎn)黄绩。

Log.e("pengtao", "max memory = " + Runtime.getRuntime().maxMemory());
Log.e("pengtao", "free memory = " + Runtime.getRuntime().freeMemory());
Log.e("pengtao", "total memory = " + Runtime.getRuntime().totalMemory());

ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
Log.e("pengtao", "memoryClass = " + Integer.toString(am.getMemoryClass()));
Log.e("pengtao", "largememoryClass = " + Integer.toString(am.getLargeMemoryClass()));

Runtime.getRuntime().maxMemory() 這個(gè)參數(shù)對(duì)應(yīng)到build.prop中的信息就是在未設(shè)置largeHeap為true時(shí)會(huì)返回heapgrowthlimit的大小,而設(shè)置了largeHeap為true后玷过,則返回heapsize大小爽丹。單位為Bytes。

getMemoryClass 所獲得的大小不受largeHeap配置影響辛蚊,永遠(yuǎn)是heapgrowthlimit中大小粤蝎。而getLargeMemoryClass則為heapsize大小,兩者單位都為M袋马。

最后要想理解totalMemoryfreeMemory概念可以看下下圖初澎。

java runtime memory
java runtime memory

上述日志代碼執(zhí)行后,在我手機(jī)上跑出來(lái)的結(jié)果如下(設(shè)置了largeHeap為true),可以把這些數(shù)據(jù)與build.prop中的數(shù)據(jù)對(duì)應(yīng)起來(lái):

12-05 16:01:50.346 29178-29178/? E/pengtao: max memory = 536870912
12-05 16:01:50.346 29178-29178/? E/pengtao: free memory = 8201514
12-05 16:01:50.346 29178-29178/? E/pengtao: total memory = 25361266
12-05 16:01:50.346 29178-29178/? E/pengtao: memoryClass = 192
12-05 16:01:50.346 29178-29178/? E/pengtao: largememoryClass = 512

注:謹(jǐn)慎設(shè)置largeHeap碑宴,因?yàn)樵酱蟮亩芽臻g意味著GC(垃圾回收)需要遍歷的對(duì)象越多软啼,時(shí)間就會(huì)越久。不過(guò)largeHeap配置是從Android 3.0開(kāi)始支持的延柠,而并發(fā)式的GC是從Android 2.3后開(kāi)始支持祸挪,所以雖說(shuō)GC時(shí)間變久了,但不會(huì)對(duì)應(yīng)用運(yùn)行造成很大影響贞间。

Android中OOM

Android應(yīng)用與Java應(yīng)用一樣贿条,避免OOM就是要剩余足夠堆內(nèi)存供應(yīng)用使用,要想內(nèi)存足夠呢增热,首先就需要避免應(yīng)用存在內(nèi)存泄漏的情況整以,內(nèi)存泄漏后,可使用的內(nèi)存空間減少峻仇,自然就會(huì)更容易產(chǎn)生OOM悄蕾。關(guān)于如何避免內(nèi)存泄漏,可以移步到本人寫得另一篇文章《Android中常見(jiàn)的內(nèi)存泄漏》中础浮。還有一個(gè)容易產(chǎn)生OOM的情況帆调,就是加載大數(shù)據(jù)到內(nèi)存中。要想更深入理解這一點(diǎn)豆同,讓我們來(lái)做個(gè)簡(jiǎn)單應(yīng)用番刊,以下為其核心代碼:

final List<byte[]> container = new ArrayList<>();
findViewById(R.id.get_memory).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        Log.e("pengtao", "max memory = " + Runtime.getRuntime().maxMemory());
        Log.e("pengtao", "free memory = " + Runtime.getRuntime().freeMemory());
        Log.e("pengtao", "total memory = " + Runtime.getRuntime().totalMem
        byte[] b = new byte[100 * 1000 * 1000];
        container.add(b);
    }
});

同樣在這段代碼運(yùn)行在一個(gè)堆限制為192M的手機(jī)上,當(dāng)點(diǎn)擊兩次按鈕影锈,應(yīng)用崩潰芹务,打印的信息如下:

12-06 21:17:44.925 4021-4021/? E/pengtao: max memory = 201326592
12-06 21:17:44.925 4021-4021/? E/pengtao: free memory = 16761904
12-06 21:17:44.925 4021-4021/? E/pengtao: total memory = 132736208
12-06 21:17:44.925 4021-4021/? I/art: Starting a blocking GC Alloc
12-06 21:17:44.933 4021-4021/? I/art: Alloc sticky concurrent mark sweep GC freed 124(6KB) AllocSpace objects, 0(0B) LOS objects, 12% free, 110MB/126MB, paused 507us total 7.280ms
... //省略幾次GC日志
12-06 21:17:44.988 4021-4021/? W/art: Throwing OutOfMemoryError "Failed to allocate a 100000012 byte allocation with 16777216 free bytes and 81MB until OOM"

該應(yīng)用崩潰的日志打印為:Throwing OutOfMemoryError "Failed to allocate a 100000012 byte allocation with 16777216 free bytes and 81MB until OOM",在崩潰前我們可以從日志中看出系統(tǒng)在努力做了幾次GC嘗試鸭廷,但卻無(wú)法釋放足夠內(nèi)存枣抱,最終只能跑出OOM異常。異常信息反應(yīng)出奔潰時(shí)的內(nèi)存狀況辆床,我們結(jié)合到打印的三個(gè)數(shù)據(jù)佳晶,和前面我們所講內(nèi)容,正好能對(duì)上號(hào)讼载。126MB即為total memory轿秧,free memory為16M,所以使用了110M空間咨堤,因堆限制為192M菇篡,堆空閑最少需要預(yù)留512K,所以還剩81M可用一喘,而這81M空間無(wú)法滿足下一次的內(nèi)存分配驱还,所以產(chǎn)生OOM。

圖片處理時(shí)

Android編程中,往往最容易出現(xiàn)OOM的地方就是在圖片處理的時(shí)候议蟆,我們先上個(gè)數(shù)據(jù):一個(gè)像素的顯示需要4字節(jié)(R灼伤、G、B咪鲜、A各占一個(gè)字節(jié)),所以一個(gè)1080x720像素的手機(jī)一個(gè)滿屏幕畫面就需要近3M內(nèi)存撞鹉,而開(kāi)發(fā)一個(gè)輕量應(yīng)用的安裝包大小也差不多就3M左右疟丙,所以說(shuō)圖片很占內(nèi)存。在Android中鸟雏,圖片的資源文件叫做Drawable享郊,存儲(chǔ)在硬盤上,不耗內(nèi)存孝鹊,但我們并無(wú)法對(duì)其進(jìn)行處理炊琉,最多只能進(jìn)行展示。而如果想對(duì)該圖片資源進(jìn)行處理又活,我們需要把這個(gè)Drawable解析為Bitmap形式裝載入內(nèi)存中苔咪。其中Android的不同版本對(duì)Bitmap的存儲(chǔ)方式還有所不同。下面是Android官方文檔中對(duì)此描述的一段話

On Android 2.3.3 (API level 10) and lower, the backing pixel data for a bitmap is stored in native memory. It is separate from the bitmap itself, which is stored in the Dalvik heap. The pixel data in native memory is not released in a predictable manner, potentially causing an application to briefly exceed its memory limits and crash. As of Android 3.0 (API level 11), the pixel data is stored on the Dalvik heap along with the associated bitmap.

bitmap分成兩個(gè)部分柳骄,一部分為bitmap對(duì)象团赏,用以存儲(chǔ)此圖片的長(zhǎng)、寬耐薯、透明度等信息舔清;另一部分為bitmap數(shù)據(jù),用以存儲(chǔ)bitmap的(A)RGB字節(jié)數(shù)據(jù)曲初。在2.3.3及以前版本中bitmap對(duì)象和bitmap數(shù)據(jù)是存儲(chǔ)在不同的內(nèi)存空間上的体谒,bitmap數(shù)據(jù)部分存儲(chǔ)在native內(nèi)存中,GC無(wú)法涉及臼婆。所以之前我們需要調(diào)用bitmap的recycle方法來(lái)顯示的告訴系統(tǒng)此處內(nèi)存可回收抒痒,而在3.0版本開(kāi)始,bitmap的的這兩部分都存儲(chǔ)在了Dalvik堆中颁褂,可以被GC機(jī)制統(tǒng)一處理评汰,也就無(wú)需用recycle了。

關(guān)于bitmap優(yōu)化痢虹,不同版本方法也不想同被去,2.3.3版本及以前,就要做到及時(shí)調(diào)用recycle來(lái)回收不在使用的bitmap奖唯,而3.0開(kāi)始可以使用BitmapFactory.Options.inBitmap這個(gè)選項(xiàng)惨缆,設(shè)置一個(gè)可復(fù)用的bitmap,這樣以后新的bitmap且大小相同的就可以直接使用這塊內(nèi)存,而無(wú)需重復(fù)申請(qǐng)內(nèi)存坯墨。4.4之后解決了對(duì)大小的限制寂汇,不同大小也可以復(fù)用該塊空間。關(guān)于inBitmap可參考官方文檔捣染。

當(dāng)有多個(gè)bitmap需要顯示時(shí)骄瓣,可以使用LruCache算法。實(shí)踐可以參考一個(gè)Github開(kāi)源庫(kù):DaVinci

作者簡(jiǎn)介
彭濤(@彭濤me) 致力于讓技術(shù)變得易懂且有趣
個(gè)人博客:http://pengtao.me, GitHub地址:https://github.com/CPPAlien

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末耍攘,一起剝皮案震驚了整個(gè)濱河市榕栏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蕾各,老刑警劉巖扒磁,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異式曲,居然都是意外死亡妨托,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門吝羞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)兰伤,“玉大人,你說(shuō)我怎么就攤上這事钧排∫角澹” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵卖氨,是天一觀的道長(zhǎng)会烙。 經(jīng)常有香客問(wèn)我,道長(zhǎng)筒捺,這世上最難降的妖魔是什么柏腻? 我笑而不...
    開(kāi)封第一講書人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮系吭,結(jié)果婚禮上五嫂,老公的妹妹穿的比我還像新娘。我一直安慰自己肯尺,他們只是感情好沃缘,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著则吟,像睡著了一般槐臀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上氓仲,一...
    開(kāi)封第一講書人閱讀 49,829評(píng)論 1 290
  • 那天水慨,我揣著相機(jī)與錄音得糜,去河邊找鬼。 笑死晰洒,一個(gè)胖子當(dāng)著我的面吹牛朝抖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播谍珊,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼治宣,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了砌滞?” 一聲冷哼從身側(cè)響起侮邀,我...
    開(kāi)封第一講書人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎布持,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體陕悬,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡题暖,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了捉超。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胧卤。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖拼岳,靈堂內(nèi)的尸體忽然破棺而出枝誊,到底是詐尸還是另有隱情,我是刑警寧澤惜纸,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布叶撒,位于F島的核電站,受9級(jí)特大地震影響耐版,放射性物質(zhì)發(fā)生泄漏祠够。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一粪牲、第九天 我趴在偏房一處隱蔽的房頂上張望古瓤。 院中可真熱鬧,春花似錦腺阳、人聲如沸落君。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)绎速。三九已至,卻和暖如春焙蚓,著一層夾襖步出監(jiān)牢的瞬間朝氓,已是汗流浹背魔市。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留赵哲,地道東北人待德。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像枫夺,于是被迫代替她去往敵國(guó)和親将宪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349

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