之前這篇,說的不是太詳細,今兒再來詳細的說說:
1.什麼是 OutOfMemoryError:
官方引用: Thrown when a request for memory is made that can not be satisfied using the available platform resources. Such a request may be made by both the running application or by an internal function of the VM.
通俗的講:就是在請求一塊記憶體的時候,當前可用資源不夠用來請求時拋出的一種錯誤。我們知道,每個 android 程式就是一個獨立 dalvik vm 執行個體,每個執行個體限制了最大記憶體佔用,如果超過了這個限制,系統就會拋出這個錯誤。所以跟整個裝置的剩餘記憶體沒太大關係,當然如果裝置剩餘記憶體都不足以再運行一個程式時,系統就會選擇 kill 部分程式以確保有足夠記憶體運行其他程式。
2.android 記憶體組成:
android 記憶體由 dalvik 和 native 2部分組成,dalvik 也就是 java 堆,建立的對象就是在這裡分配的,而 native 是通過 c/c++ 方式申請的記憶體,Bitmap 就是以一種方式分配的(android3.0 以後,系統預設是通過 dalvik 分配的)。當然無論以何種方式分配,2部分加起來不能超過 android 對單個程式的記憶體限制。
3.記憶體限制大小:
1 ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);2 activityManager.getMemoryClass();
以上方法會返回以 M 為單位的數字,可能在不同的平台或者裝置上值都不太一樣,比如:HTC G7 預設 24M,Galaxy 36M,emulator-2.3 24M,等等。
4.程式實際佔用:
以一個簡單的 android 程式為例,該程式是用 eclipse adt 自動產生的最簡單的一個 android 項目,只有1個 activity 和 adt 自動產生的 res 目錄,測試環境:emulator-2.3.3
啟動該程式,命令列運行:
adb shell dumpsys meminfo com.mem.demo
執行結果:
Applications Memory Usage (kB):Uptime: 1195344 Realtime: 1195344** MEMINFO in pid 333 [com.mem.demo] ** native dalvik other total -------------------------------------------------------- | size: 3968 5379 N/A 9347 | | | | allocated: 3964 2649 N/A 6613 | -------------------------------------------------------- free: 3 2730 N/A 2733 (Pss): 553 449 2516 3518 (shared dirty): 2272 1868 6648 10788 (priv dirty): 420 32 1140 1592 Objects Views: 0 ViewRoots: 0 AppContexts: 0 Activities: 0 Assets: 2 AssetManagers: 2 Local Binders: 5 Proxy Binders: 10Death Recipients: 0 OpenSSL Sockets: 0 SQL heap: 0 MEMORY_USED: 0 PAGECACHE_OVERFLOW: 0 MALLOC_SIZE: 0 Asset Allocations zip:/data/app/com.mem.demo-1.apk:/resources.arsc: 1K
從上面被框出來的部分可以看出,一個最簡單的 android 程式在啟動後都有 6m 左右記憶體的佔用(上面是 6613kb)。那這 6m 的記憶體除了該 android 自己的資源和類之外,其他的還有什麼呢:
簡單說:在初始化的時候會 preload 一些東西,這些就包括 classes 和系統資源,就是系統的一些布局啊,圖片啊,等等,在 android 完成啟動以後,這部分就通過記憶體共用的方式共用給其他程式,可以讓其他程式可以調用這部分資源,代碼可以參考:http://goo.gl/EKvCV,android 整個啟動流程可以參考:http://goo.gl/K36Lr 。
5.發生 OOM :
為了製造 OOM,我們對上面最簡單的程式進行了改寫:
package com.mem.demo;import android.app.Activity;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.os.Bundle;public class DemoActivity extends Activity { Bitmap map1, map2, map3, map4; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); map1 = BitmapFactory.decodeResource(getResources(), R.drawable.big1); map2 = BitmapFactory.decodeResource(getResources(), R.drawable.big2); map3 = BitmapFactory.decodeResource(getResources(), R.drawable.big3); map4 = BitmapFactory.decodeResource(getResources(), R.drawable.big4); }}
其中:big1 到 big4 都是 1600*900 解析度的圖片,放在 drawbable-hdpi 檔案夾下面,啟動程式,發生了 OOM:
05-08 07:44:44.372: E/dalvikvm-heap(386): 5760000-byte external allocation too large for this process.05-08 07:44:44.412: I/dalvikvm-heap(386): Clamp target GC heap from 25.099MB to 24.000MB05-08 07:44:44.412: E/GraphicsJNI(386): VM won't let us allocate 5760000 bytes05-08 07:44:44.412: D/dalvikvm(386): GC_FOR_MALLOC freed 0K, 53% free 2548K/5379K, external 18500K/20548K, paused 36ms05-08 07:44:44.422: D/skia(386): --- decoder->decode returned false05-08 07:44:44.422: D/AndroidRuntime(386): Shutting down VM05-08 07:44:44.432: W/dalvikvm(386): threadid=1: thread exiting with uncaught exception (group=0x40015560)05-08 07:44:44.442: E/AndroidRuntime(386): FATAL EXCEPTION: main05-08 07:44:44.442: E/AndroidRuntime(386): java.lang.OutOfMemoryError: bitmap size exceeds VM budget05-08 07:44:44.442: E/AndroidRuntime(386): at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)05-08 07:44:44.442: E/AndroidRuntime(386): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:460)05-08 07:44:44.442: E/AndroidRuntime(386): at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:336)05-08 07:44:44.442: E/AndroidRuntime(386): at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:359)05-08 07:44:44.442: E/AndroidRuntime(386): at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:385)05-08 07:44:44.442: E/AndroidRuntime(386): at com.mem.demo.DemoActivity.onCreate(DemoActivity.java:20)05-08 07:44:44.442: E/AndroidRuntime(386): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)05-08 07:44:44.442: E/AndroidRuntime(386): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1611)05-08 07:44:44.442: E/AndroidRuntime(386): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1663)05-08 07:44:44.442: E/AndroidRuntime(386): at android.app.ActivityThread.access$1500(ActivityThread.java:117)05-08 07:44:44.442: E/AndroidRuntime(386): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:931)05-08 07:44:44.442: E/AndroidRuntime(386): at android.os.Handler.dispatchMessage(Handler.java:99)05-08 07:44:44.442: E/AndroidRuntime(386): at android.os.Looper.loop(Looper.java:123)05-08 07:44:44.442: E/AndroidRuntime(386): at android.app.ActivityThread.main(ActivityThread.java:3683)05-08 07:44:44.442: E/AndroidRuntime(386): at java.lang.reflect.Method.invokeNative(Native Method)05-08 07:44:44.442: E/AndroidRuntime(386): at java.lang.reflect.Method.invoke(Method.java:507)05-08 07:44:44.442: E/AndroidRuntime(386): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)05-08 07:44:44.442: E/AndroidRuntime(386): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)05-08 07:44:44.442: E/AndroidRuntime(386): at dalvik.system.NativeStart.main(Native Method)
從日誌上看,記憶體不足發生在 decode big4 這個圖片的時候,我們對記憶體情況進行列印:
Applications Memory Usage (kB):Uptime: 958383 Realtime: 958383** MEMINFO in pid 386 [com.mem.demo] ** native dalvik other total size: 25144 5379 N/A 30523 allocated: 20799 2614 N/A 23413 free: 60 2765 N/A 2825 (Pss): 494 426 18494 19414 (shared dirty): 2288 1876 5292 9456 (priv dirty): 360 28 17836 18224
從記憶體列印情況看出,當前已經分配了 23m 左右記憶體,同時結合 logcat 錯誤記錄檔,big4 這個圖片需要申請 5760000byte,即:5.76m 的記憶體,加起來就是 29m 左右記憶體,我們測試環境是 emulator2.3.3 預設是
24m,記憶體不足以申請這麼多,所以拋出了OOM。
那為什麼區區3,4張圖片就會讓 android 程式記憶體不足?
裝置限制是一方面,像上面第3點說的,每個 android 裝置的記憶體限制不一樣,這個程式在模擬器上會有問題,在其他裝置上,比如:galaxy 就不會有問題。最主要的還是跟圖片所佔記憶體有關係,那麼一張圖片到底
佔用多少記憶體呢,java 沒有 c 的 sizeof() 函數,無法準確去量化這個數值,但是可以有粗略的計算方法:
寬 * 高 * 每個像素所佔的 bytes
寬度和高度這個很容易獲得,那每個像素所佔的 bytes 呢,這個主要取決於 decode 圖片的方式:
Bitmap.Config ALPHA_8 Each pixel is stored as a single translucency (alpha) channel. Bitmap.Config ARGB_4444 This field is deprecated. Because of the poor quality of this configuration, it is advised to use ARGB_8888 instead. Bitmap.Config ARGB_8888 Each pixel is stored on 4 bytes. Bitmap.Config RGB_565 Each pixel is stored on 2 bytes and only the RGB channels are encoded: red is stored with 5 bits of precision (32 possible values), green is store d with 6 bits of precision (64 possible values) and blue is stored with 5 bits of precision.
以上是官方文檔對 Bitmap.Config 類的描述,所以,如果以 ARGB_8888 的方式 decode,那個每個像素佔用4個 bytes,而如果用 RGB_565 就佔用2個 bytes。
我們計算一下,在 2.3 以後,程式內建的圖片資源,都預設以 ARGB_8888 的方式,而在此以之前是以 RGB_565 的方式(不確定,待驗證),所以顏色會有損耗,典型的就是如果有漸層色的話,會出現光圈。
所以,計算如下:
1600 * 900 * 4 = 5760000
這個 5760000 也就是上面 logcat 錯誤記錄檔裡面所提到的申請數字,當然在實際用命令列印出的記憶體情況上看,比這個數字要大,是因為這隻是圖片像素的記憶體,還有一些屬性,變數和類本身沒有計算在內。
未完,待續 。。。