true studio outofmemoryerror outofmemory largeheap images example bitmaps android out-of-memory

outofmemoryerror - out of memory android studio



Límite de mapa de bits de Android: prevención de java.lang.OutOfMemory (3)

Actualmente estoy luchando con un comportamiento extraño de la plataforma Android: el límite de memoria de montón de Bitmap / Java. Dependiendo del dispositivo, Android limita el desarrollador de la aplicación a 16, 24 o 32 MiB de espacio de almacenamiento Java (o puede encontrar cualquier valor aleatorio en un teléfono rooteado). Podría decirse que es bastante pequeño, pero relativamente sencillo, ya que puedo medir el uso con las siguientes API:

Runtime rt = Runtime.getRuntime(); long javaBytes = rt.totalMemory() - rt.freeMemory(); long javaLimit = rt.maxMemory();

Suficientemente fácil; Ahora para el giro. En Android, los mapas de bits, con pocas excepciones, se almacenan en el montón nativo y no cuentan para el montón de Java. Algunos desarrolladores puristas de Google con ojos brillantes decidieron que esto era "malo" y permitieron que el desarrollador obtuviera "más de lo que les corresponde". Por lo tanto, hay una pequeña pieza de código que calcula el uso de la memoria nativa en que incurren los mapas de bits y posiblemente otros recursos, y la suma con el montón de Java y, si revisas ... java.lang.OutOfMemory. Ay

Pero no es gran cosa. Tengo muchos mapas de bits y no los necesito a todos todo el tiempo. Puedo "paginar" algunos de los que no se están utilizando en este momento:

Por lo tanto, para el intento n. ° 1, he refactorizado el código para poder envolver cada carga de mapa de bits con un try / catch:

while(true) { try { return BitmapFactory.decodeResource(context.getResources(), android_id, bitmapFactoryOptions); } catch (java.lang.OutOfMemory e) { // Do some logging // Now free some space (the code below is a simplified version of the real thing) Bitmap victim = selectVictim(); victim.recycle(); System.gc(); // REQUIRED; else, weird behavior ensues } }

Mira, aquí hay un pequeño fragmento de registro que muestra mi código detectando la excepción y reciclando algunos mapas de bits:

E/Epic (23221): OUT_OF_MEMORY (caught java.lang.OutOfMemory) I/Epic (23221): ArchPlatform[android].logStats() - I/Epic (23221): LoadedClassCount=0.00M I/Epic (23221): GlobalAllocSize=0.00M I/Epic (23221): GlobalFreedSize=0.02M I/Epic (23221): GlobalExternalAllocSize=0.00M I/Epic (23221): GlobalExternalFreedSize=0.00M I/Epic (23221): EpicPixels=26.6M (this is 4 * #pixels in all loaded bitmaps) I/Epic (23221): NativeHeapSize=29.4M I/Epic (23221): NativeHeapAllocSize=25.2M I/Epic (23221): ThreadAllocSize=0.00M I/Epic (23221): totalMemory()=9.1M I/Epic (23221): maxMemory()=32.0M I/Epic (23221): freeMemory()=4.4M W/Epic (23221): Recycling bitmap ''game_word_puzzle_11_aniframe_005'' I/Epic (23221): BITMAP_RECYCLING: recycled 1 bitmaps worth 1.1M). age=294

Note cómo totalMemory - freeMemory es solo 4.7 MiB, pero con ~ 26? MiB de memoria nativa ocupada por mapas de bits, estamos en el rango de 31/32 MiB donde alcanzamos el límite. Todavía estoy un poco confundido aquí ya que mi cuenta de todos los mapas de bits cargados es de 26.6 MiB, pero el tamaño de asignación nativo es de solo 25.2 MiB. Así que estoy contando algo mal. Pero todo está en el estadio de béisbol y definitivamente demuestra que la "suma" de la piscina cruzada ocurre con el límite de memoria.

Pensé que lo tenía arreglado. Pero no, Android no se rendiría tan fácilmente ...

Esto es lo que obtengo de dos de mis cuatro dispositivos de prueba:

I/dalvikvm-heap(17641): Clamp target GC heap from 32.687MB to 32.000MB D/dalvikvm(17641): GC_FOR_MALLOC freed <1K, 41% free 4684K/7815K, external 24443K/24443K, paused 24ms D/dalvikvm(17641): GC_EXTERNAL_ALLOC freed <1K, 41% free 4684K/7815K, external 24443K/24443K, paused 29ms E/dalvikvm-heap(17641): 1111200-byte external allocation too large for this process. E/dalvikvm(17641): Out of memory: Heap Size=7815KB, Allocated=4684KB, Bitmap Size=24443KB, Limit=32768KB E/dalvikvm(17641): Trim info: Footprint=7815KB, Allowed Footprint=7815KB, Trimmed=880KB E/GraphicsJNI(17641): VM won''t let us allocate 1111200 bytes I/dalvikvm-heap(17641): Clamp target GC heap from 32.686MB to 32.000MB D/dalvikvm(17641): GC_FOR_MALLOC freed <1K, 41% free 4684K/7815K, external 24443K/24443K, paused 17ms I/DEBUG ( 1505): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** I/DEBUG ( 1505): Build fingerprint: ''verizon_wwe/htc_mecha/mecha:2.3.4/GRJ22/98797:user/release-keys'' I/DEBUG ( 1505): pid: 17641, tid: 17641 I/DEBUG ( 1505): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000000 I/DEBUG ( 1505): r0 0055dab8 r1 00000000 r2 00000000 r3 0055dadc I/DEBUG ( 1505): r4 0055dab8 r5 00000000 r6 00000000 r7 00000000 I/DEBUG ( 1505): r8 000002b7 r9 00000000 10 00000000 fp 00000384 I/DEBUG ( 1505): ip 0055dab8 sp befdb0c0 lr 00000000 pc ab14f11c cpsr 60000010 I/DEBUG ( 1505): d0 414000003f800000 d1 2073646565637834 I/DEBUG ( 1505): d2 4de4b8bc426fb934 d3 42c80000007a1f34 I/DEBUG ( 1505): d4 00000008004930e0 d5 0000000000000000 I/DEBUG ( 1505): d6 0000000000000000 d7 4080000080000000 I/DEBUG ( 1505): d8 0000025843e7c000 d9 c0c0000040c00000 I/DEBUG ( 1505): d10 40c0000040c00000 d11 0000000000000000 I/DEBUG ( 1505): d12 0000000000000000 d13 0000000000000000 I/DEBUG ( 1505): d14 0000000000000000 d15 0000000000000000 I/DEBUG ( 1505): d16 afd4242840704ab8 d17 0000000000000000 I/DEBUG ( 1505): d18 0000000000000000 d19 0000000000000000 I/DEBUG ( 1505): d20 0000000000000000 d21 0000000000000000 I/DEBUG ( 1505): d22 0000000000000000 d23 0000000000000000 I/DEBUG ( 1505): d24 0000000000000000 d25 0000000000000000 I/DEBUG ( 1505): d26 0000000000000000 d27 0000000000000000 I/DEBUG ( 1505): d28 00ff00ff00ff00ff d29 00ff00ff00ff00ff I/DEBUG ( 1505): d30 0000000000000000 d31 3fe55167807de022 I/DEBUG ( 1505): scr 68000012

Eso es un accidente nativo. Un segfault no menos (sig11). Por definición, una falla de seguridad es SIEMPRE un error. Esto es absolutamente un error de Android en el código nativo que controla GC y / o el límite de comprobación. Pero sigue siendo mi aplicación la que se bloquea, lo que resulta en malas críticas, devoluciones y menores ventas.

Así que tengo que calcular el límite yo mismo. Excepto que he luchado aquí. Intenté sumar los píxeles yo mismo (EpicPixels), pero igual golpeo el memcrash periódicamente, por lo que no estoy contando nada. Intenté agregar los javaBytes (total - gratis) a NativeHeapAllocSize, pero esto causaría ocasionalmente que mi aplicación se convirtiera en "anoréxica", liberando y liberando mapas de bits hasta que no quedara nada para purgar.

  1. ¿Alguien sabe el cálculo exacto utilizado para calcular el límite de memoria y activar java.lang.OutOfMemory?

  2. ¿Alguien más ha golpeado este problema y trabajado a través de él? ¿Tienes alguna perla de sabiduría?

  3. ¿Alguien sabe qué empleado de Google ideó este plan para poder golpearlo por arruinar unas 40 horas de mi vida? j / k

RESPUESTA: El límite es para NativeHeapAllocSize <maxMemory (); Sin embargo, debido a la fragmentación de la memoria, Android se bloquea mucho antes del límite real. Por lo tanto, tiene que limitarse a un valor algo menor que el límite real. Este "factor de seguridad" depende de la aplicación, pero algunos MiB parecen funcionar para la mayoría de las personas. (¿Puedo decir que estoy impresionado por lo roto que es este comportamiento)


El límite varía según cada dispositivo (use el tercer enlace si desea cargar el mapa de bits como está) o aquí tiene algunos trucos para evitar ese problema, como:

  • use onLowMemory () de la clase Application para liberar algo de memoria y evitar el bloqueo.
  • Indique el tamaño deseado para el mapa de bits antes de decodificarlo. Compruebe que los enlaces para más información:

http://davidjhinson.wordpress.com/2010/05/19/scarce-commodities-google-android-memory-and-bitmaps/

Extraño problema de falta de memoria al cargar una imagen en un objeto de mapa de bits

Este enlace muestra para comprobar el montón

BitmapFactory OOM me vuelve loco

  • Y por supuesto liberar la memoria de viejos mapas de bits.

Usa este snipplet, trabajado para mi

/** * Checks if a bitmap with the specified size fits in memory * @param bmpwidth Bitmap width * @param bmpheight Bitmap height * @param bmpdensity Bitmap bpp (use 2 as default) * @return true if the bitmap fits in memory false otherwise */ public static boolean checkBitmapFitsInMemory(long bmpwidth,long bmpheight, int bmpdensity ){ long reqsize=bmpwidth*bmpheight*bmpdensity; long allocNativeHeap = Debug.getNativeHeapAllocatedSize(); final long heapPad=(long) Math.max(4*1024*1024,Runtime.getRuntime().maxMemory()*0.1); if ((reqsize + allocNativeHeap + heapPad) >= Runtime.getRuntime().maxMemory()) { return false; } return true; }

Aquí hay un ejemplo de uso.

BitmapFactory.Options bmpFactoryOptions = new BitmapFactory.Options(); bmpFactoryOptions.inJustDecodeBounds=true; BitmapFactory.decodeFile(path,bmpFactoryOptions); if ( (runInSafeMemoryMode()) && (!Manager.checkBitmapFitsInMemory(bmpFactoryOptions.outWidth, bmpFactoryOptions.outHeight, 2)) ){ Log.w(TAG,"Aborting bitmap load for avoiding memory crash"); return null; }


OK, entonces estoy empezando a sospechar que el límite en modo nativo se aplica en el tamaño total de java java + la memoria nativa utilizada.

El límite se basa en NativeHeapAllocSize vs. maxMemory (). Verás a continuación que me estoy estrellando asignando ~ 1 MiB mientras estoy en 22.0 MiB / 24 MiB. El límite es un límite superior en la cantidad de memoria que puede asignar. Esto es lo que me tiró por un rato. El accidente ocurre significativamente antes de llegar al límite. Por lo tanto, la necesidad de un valor "memoryPad" en la solución, ya que al tratar de asignar 23.999 MiB / 24 MiB, se producirá un bloqueo casi el 100% del tiempo. Entonces, si el límite es de 24 MiB, ¿cuánto puede usar con seguridad? Desconocido. 20 MiB parece funcionar. 22 MiB parece funcionar. Estoy nervioso por acercarme más que eso. La cantidad varía según la fragmentación del espacio de memoria malloc en el proceso nativo. Y, por supuesto, no hay forma de medir nada de esto, así que errar en el lado seguro.

07-31 18:37:19.031: WARN/Epic(3118): MEMORY-USED: 27.3M = 4.2M + 23.0M. jf=1.7M, nhs=23.3M, nhf=0.0M 07-31 18:37:19.081: INFO/Epic(3118): ArchPlatform[android].logStats() - 07-31 18:37:19.081: INFO/Epic(3118): LoadedClassCount=0.00M 07-31 18:37:19.081: INFO/Epic(3118): GlobalAllocSize=0.02M 07-31 18:37:19.081: INFO/Epic(3118): GlobalFreedSize=0.05M 07-31 18:37:19.081: INFO/Epic(3118): GlobalExternalAllocSize=0.00M 07-31 18:37:19.081: INFO/Epic(3118): GlobalExternalFreedSize=0.00M 07-31 18:37:19.081: INFO/Epic(3118): EpicPixels=17.9M 07-31 18:37:19.081: INFO/Epic(3118): NativeHeapSize=22.2M 07-31 18:37:19.081: INFO/Epic(3118): NativeHeapFree=0.07M 07-31 18:37:19.081: INFO/Epic(3118): NativeHeapAllocSize=22.0M 07-31 18:37:19.081: INFO/Epic(3118): ThreadAllocSize=0.12M 07-31 18:37:19.081: INFO/Epic(3118): totalMemory()=5.7M 07-31 18:37:19.081: INFO/Epic(3118): maxMemory()=24.0M 07-31 18:37:19.081: INFO/Epic(3118): freeMemory()=1.6M 07-31 18:37:19.081: INFO/Epic(3118): app.mi.availMem=126.5M 07-31 18:37:19.081: INFO/Epic(3118): app.mi.threshold=16.0M 07-31 18:37:19.081: INFO/Epic(3118): app.mi.lowMemory=false 07-31 18:37:19.081: INFO/Epic(3118): dbg.mi.dalvikPrivateDirty=0.00M 07-31 18:37:19.081: INFO/Epic(3118): dbg.mi.dalvikPss=0.00M 07-31 18:37:19.081: INFO/Epic(3118): dbg.mi.dalvikSharedDirty=0.00M 07-31 18:37:19.081: INFO/Epic(3118): dbg.mi.nativePrivateDirty=0.00M 07-31 18:37:19.081: INFO/Epic(3118): dbg.mi.nativePss=0.00M 07-31 18:37:19.081: INFO/Epic(3118): dbg.mi.nativeSharedDirty=0.00M 07-31 18:37:19.081: INFO/Epic(3118): dbg.mi.otherPrivateDirty=0.02M 07-31 18:37:19.081: INFO/Epic(3118): dbg.mi.otherPss0.02M 07-31 18:37:19.081: INFO/Epic(3118): dbg.mi.otherSharedDirty=0.00M 07-31 18:37:19.081: ERROR/dalvikvm-heap(3118): 1111200-byte external allocation too large for this process. 07-31 18:37:19.081: ERROR/dalvikvm(3118): Out of memory: Heap Size=6535KB, Allocated=4247KB, Bitmap Size=17767KB 07-31 18:37:19.081: ERROR/GraphicsJNI(3118): VM won''t let us allocate 1111200 bytes

El código para imprimir todo eso:

public static void logMemoryStats() { String text = ""; text += "/nLoadedClassCount=" + toMib(android.os.Debug.getLoadedClassCount()); text += "/nGlobalAllocSize=" + toMib(android.os.Debug.getGlobalAllocSize()); text += "/nGlobalFreedSize=" + toMib(android.os.Debug.getGlobalFreedSize()); text += "/nGlobalExternalAllocSize=" + toMib(android.os.Debug.getGlobalExternalAllocSize()); text += "/nGlobalExternalFreedSize=" + toMib(android.os.Debug.getGlobalExternalFreedSize()); text += "/nEpicPixels=" + toMib(EpicBitmap.getGlobalPixelCount()*4); text += "/nNativeHeapSize=" + toMib(android.os.Debug.getNativeHeapSize()); text += "/nNativeHeapFree=" + toMib(android.os.Debug.getNativeHeapFreeSize()); text += "/nNativeHeapAllocSize=" + toMib(android.os.Debug.getNativeHeapAllocatedSize()); text += "/nThreadAllocSize=" + toMib(android.os.Debug.getThreadAllocSize()); text += "/ntotalMemory()=" + toMib(Runtime.getRuntime().totalMemory()); text += "/nmaxMemory()=" + toMib(Runtime.getRuntime().maxMemory()); text += "/nfreeMemory()=" + toMib(Runtime.getRuntime().freeMemory()); android.app.ActivityManager.MemoryInfo mi1 = new android.app.ActivityManager.MemoryInfo(); ActivityManager am = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE); am.getMemoryInfo(mi1); text += "/napp.mi.availMem=" + toMib(mi1.availMem); text += "/napp.mi.threshold=" + toMib(mi1.threshold); text += "/napp.mi.lowMemory=" + mi1.lowMemory; android.os.Debug.MemoryInfo mi2 = new android.os.Debug.MemoryInfo(); Debug.getMemoryInfo(mi2); text += "/ndbg.mi.dalvikPrivateDirty=" + toMib(mi2.dalvikPrivateDirty); text += "/ndbg.mi.dalvikPss=" + toMib(mi2.dalvikPss); text += "/ndbg.mi.dalvikSharedDirty=" + toMib(mi2.dalvikSharedDirty); text += "/ndbg.mi.nativePrivateDirty=" + toMib(mi2.nativePrivateDirty); text += "/ndbg.mi.nativePss=" + toMib(mi2.nativePss); text += "/ndbg.mi.nativeSharedDirty=" + toMib(mi2.nativeSharedDirty); text += "/ndbg.mi.otherPrivateDirty=" + toMib(mi2.otherPrivateDirty); text += "/ndbg.mi.otherPss" + toMib(mi2.otherPss); text += "/ndbg.mi.otherSharedDirty=" + toMib(mi2.otherSharedDirty); EpicLog.i("ArchPlatform[android].logStats() - " + text); }