Ограничение на растровое изображение Android - предотвращение java.lang.OutOfMemory

В настоящее время я борюсь со странным поведением платформы Android - ограничением памяти кучи Bitmap / Java. В зависимости от устройства Android ограничивает разработчика приложения 16, 24 или 32 МБ пространства кучи Java (или вы можете найти любое случайное значение на рутированном телефоне). Это, возможно, довольно мало, но относительно просто, поскольку я могу измерить использование с помощью следующих API:

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

Достаточно просто; теперь поворот. В Android растровые изображения, за некоторыми исключениями, хранятся в собственной куче и не учитываются в куче Java. Какой-то проницательный и пуристский разработчик в Google решил, что это «плохо», и позволил разработчику получить «больше, чем полагается». Итак, есть этот красивый небольшой фрагмент кода, который вычисляет использование собственной памяти, вызванное растровыми изображениями и, возможно, другими ресурсами, и суммирует это с кучей Java, и если вы перейдете ... java.lang.OutOfMemory. ой

Но ничего страшного. У меня много растровых изображений, и я не нуждаюсь в них все время. Я могу "вывести" на страницу некоторые из тех, которые не используются в данный момент:

Итак, для попытки №1 я реорганизовал код, чтобы я мог обернуть каждую загрузку растрового изображения с помощью 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
    }
}

Смотрите, вот небольшой отрывок из журнала, показывающий, что мой код перехватывает исключение и повторно использует растровые изображения:

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

Обратите внимание, что totalMemory - freeMemory занимает всего 4,7 МБ, но с ~ 26? МиБ собственной памяти, занятой растровыми изображениями, мы находимся в диапазоне 31/32 МБ, где мы достигли предела. Я все еще немного сбит с толку, так как мой текущий счет всех загруженных растровых изображений составляет 26,6 МБ, но собственный размер выделения составляет всего 25,2 МБ. Значит я что-то не так считаю. Но это все приблизительно и однозначно демонстрирует "суммирование" кросс-пула, происходящее с пределом памяти.

Я ДУМАЛ, что исправил это. Но нет, Android так легко не сдался бы ...

Вот что я получил на двух из четырех моих тестовых устройств:

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

Это родной сбой. Не менее segfault (sig11). По определению segfault ВСЕГДА является ошибкой. Это абсолютно ошибка Android в собственном коде, обрабатывающем сборщик мусора и / или проверке ограничения памяти. Но мое приложение по-прежнему дает сбой, что приводит к плохим отзывам, возвратам и снижению продаж.

Итак, я должен вычислить предел сам. За исключением того, что я боролся здесь. Я сам пробовал складывать пиксели (EpicPixels), но все равно периодически вылетаю из памяти, поэтому я что-то занижаю. Я попытался добавить javaBytes (полностью - бесплатно) в NativeHeapAllocSize, но иногда это приводило к тому, что мое приложение становилось «анорексичным», освобождая и освобождая растровые изображения до тех пор, пока нечего было очищать.

  1. Кто-нибудь знает точную вычисление, используемое для вычисления предела памяти и запуска java.lang.OutOfMemory?

  2. Кто-нибудь еще сталкивался с этой проблемой и работал над ней? Есть ли у вас какие-нибудь жемчужины мудрости?

  3. Кто-нибудь знает, какой сотрудник Google придумал эту схему, чтобы я мог ударить его за то, что он испортил ~ 40 часов моей жизни? j / k

ОТВЕТ: Предел указан для NativeHeapAllocSize

25
задан Ian Terrell 7 May 2014 в 17:36
поделиться