Android中的OutOfMemoryError

OOM 的產生

在使用C或C++語言時,我們可操作的內存空間就是整個設備的物理內存,程序員需要自己聲明內存空間,也需要自己在恰當的時機釋放掉內存,一旦出錯就會造成內存泄漏。而Java語言為了解決這個問題,在操作系統之上創造了一個Java虛擬機(JVM),讓Java語言編譯后的字節碼運行在此虛擬機之上。啟動一個Java應用,會首先啟動JVM,JVM 會向操作系統申請所需內存,然后把內存分成為棧內存和堆內存。堆內存用以存放對象實例,并可被Java回收機制回收,一旦剩余堆內存空間不夠申請新對象時就會產生OutOfMemoryError異常。

Android內存管理

Android的Dalvik虛擬機(DVM)是參考JVM做出來的,所以大同小異。最主要的兩個區別是:一,DVM 基于寄存器,而JVM基于棧來進行局部變量的操作,當然在性能上DVM會更快;二,在DVM上運行的是被進一步處理的JAVA字節碼,后綴為.dex,.dex 是把Java應用中所有的.class文件合并而成,縮減了包的體積。Android中的 DVM 如 JVM 一樣對每個應用可使用的最大內存空間做了限制,每臺設備出廠之前廠家就對單個 DVM 實例可使用的最大內存進行了限定。Android屆的第一款手機HTC G1的大小為16M。這些信息儲存在手機中 /system/build.prop配置文件中,如下我這是我使用的華為6p plus手機的相關信息

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為一個應用初始分配的堆大小,越大意味著應用第一次啟動時越流暢,但也意味著內存耗用越快。

dalvik.vm.heapgrowthlimit 這就是所謂的單個應用可使用的最大內存堆大小。

dalvik.vm.heapsize 此項表示應用在manifest中配置android:largeHeap="true"時可使用的最大內存堆大小。

獲取內存配置

有些小伙伴可能使用過以下方法來獲得內存信息,但可能就和最初的我一樣,不知道這些獲得的數據到底是啥意思。下面我們就來說說每個方法所獲得的數據的意義和特點。

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() 這個參數對應到build.prop中的信息就是在未設置largeHeap為true時會返回heapgrowthlimit的大小,而設置了largeHeap為true后,則返回heapsize大小。單位為Bytes。

getMemoryClass 所獲得的大小不受largeHeap配置影響,永遠是heapgrowthlimit中大小。而getLargeMemoryClass則為heapsize大小,兩者單位都為M。

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

java runtime memory
java runtime memory

上述日志代碼執行后,在我手機上跑出來的結果如下(設置了largeHeap為true),可以把這些數據與build.prop中的數據對應起來:

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

注:謹慎設置largeHeap,因為越大的堆空間意味著GC(垃圾回收)需要遍歷的對象越多,時間就會越久。不過largeHeap配置是從Android 3.0開始支持的,而并發式的GC是從Android 2.3后開始支持,所以雖說GC時間變久了,但不會對應用運行造成很大影響。

Android中OOM

Android應用與Java應用一樣,避免OOM就是要剩余足夠堆內存供應用使用,要想內存足夠呢,首先就需要避免應用存在內存泄漏的情況,內存泄漏后,可使用的內存空間減少,自然就會更容易產生OOM。關于如何避免內存泄漏,可以移步到本人寫得另一篇文章《Android中常見的內存泄漏》中。還有一個容易產生OOM的情況,就是加載大數據到內存中。要想更深入理解這一點,讓我們來做個簡單應用,以下為其核心代碼:

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);
    }
});

同樣在這段代碼運行在一個堆限制為192M的手機上,當點擊兩次按鈕,應用崩潰,打印的信息如下:

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"

該應用崩潰的日志打印為:Throwing OutOfMemoryError "Failed to allocate a 100000012 byte allocation with 16777216 free bytes and 81MB until OOM",在崩潰前我們可以從日志中看出系統在努力做了幾次GC嘗試,但卻無法釋放足夠內存,最終只能跑出OOM異常。異常信息反應出奔潰時的內存狀況,我們結合到打印的三個數據,和前面我們所講內容,正好能對上號。126MB即為total memory,free memory為16M,所以使用了110M空間,因堆限制為192M,堆空閑最少需要預留512K,所以還剩81M可用,而這81M空間無法滿足下一次的內存分配,所以產生OOM。

圖片處理時

Android編程中,往往最容易出現OOM的地方就是在圖片處理的時候,我們先上個數據:一個像素的顯示需要4字節(R、G、B、A各占一個字節),所以一個1080x720像素的手機一個滿屏幕畫面就需要近3M內存,而開發一個輕量應用的安裝包大小也差不多就3M左右,所以說圖片很占內存。在Android中,圖片的資源文件叫做Drawable,存儲在硬盤上,不耗內存,但我們并無法對其進行處理,最多只能進行展示。而如果想對該圖片資源進行處理,我們需要把這個Drawable解析為Bitmap形式裝載入內存中。其中Android的不同版本對Bitmap的存儲方式還有所不同。下面是Android官方文檔中對此描述的一段話

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分成兩個部分,一部分為bitmap對象,用以存儲此圖片的長、寬、透明度等信息;另一部分為bitmap數據,用以存儲bitmap的(A)RGB字節數據。在2.3.3及以前版本中bitmap對象和bitmap數據是存儲在不同的內存空間上的,bitmap數據部分存儲在native內存中,GC無法涉及。所以之前我們需要調用bitmap的recycle方法來顯示的告訴系統此處內存可回收,而在3.0版本開始,bitmap的的這兩部分都存儲在了Dalvik堆中,可以被GC機制統一處理,也就無需用recycle了。

關于bitmap優化,不同版本方法也不想同,2.3.3版本及以前,就要做到及時調用recycle來回收不在使用的bitmap,而3.0開始可以使用BitmapFactory.Options.inBitmap這個選項,設置一個可復用的bitmap,這樣以后新的bitmap且大小相同的就可以直接使用這塊內存,而無需重復申請內存。4.4之后解決了對大小的限制,不同大小也可以復用該塊空間。關于inBitmap可參考官方文檔

當有多個bitmap需要顯示時,可以使用LruCache算法。實踐可以參考一個Github開源庫:DaVinci

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,936評論 6 535
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,744評論 3 421
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,879評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,181評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,935評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,325評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,384評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,534評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,084評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,892評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,623評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,322評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,735評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,990評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,800評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,084評論 2 375

推薦閱讀更多精彩內容