Android Bitmap Limit - Предотвращение 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 (пойман 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 (это 4 * # пикселя во всех загруженных растровых изображениях)
I/Epic    (23221): NativeHeapSize=29,4 млн.
I/Epic    (23221): NativeHeapAllocSize=25,2 млн.
I/Epic    (23221): ThreadAllocSize=0,00M
I/Epic    (23221): totalMemory()=9,1M
I/Epic    (23221): maxMemory()=32,0M
I/Epic    (23221): freeMemory()=4,4 млн.
W/Epic    (23221): переработка растрового изображения 'game_word_puzzle_11_aniframe_005'
I/Epic    (23221): BITMAP_RECYCLING: переработано 1 растровое изображение стоимостью 1,1 млн.). возраст = 294

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

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

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

I/dalvikvm-heap(17641): Зафиксировать целевую кучу GC от 32.687 МБ до 32.000 МБ D/dalvikvm(17641): GC_FOR_MALLOC освобождено <1K, 41% свободно, 4684K/7815K, внешнее 24443K/24443K, приостановлено на 24 мс D / dalvik1.): GC_EXTERNAL_ALLOC освобожден <1K, 41% свободен 4684K/7815K, внешний 24443K/24443K, приостановлен 29 мс E/dalvikvm-heap(17641): внешнее выделение 1111200 байт слишком велико для этого процесса.
E/dalvikvm(17641): Недостаточно памяти: размер кучи =7815 КБ, выделенный =4684 КБ, размер растрового изображения =24443 КБ, предел =32768 КБ E / dalvikvm (17641): информация обрезки: Footprint=7815KB, допустимый размер =7815KB, Trimmed=880 E/GraphicsJNI(17641): VM не позволяет выделять 1111200 байт I/dalvikvm-heap(17641): Зарезать целевую кучу GC от 32,686 МБ до 32 000 МБ D/dalvikvm(17641): GC_FOR_MALLOC освобожден <1K, 41% бесплатно 4684K/7815K, внешний 24443K/24443K, пауза 17 мс I/DEBUG   ( 1505): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
I/DEBUG   ( 1505): Создание отпечатка пальца: 'verizon_wwe/htc_mecha/mecha:2.3.4/GRJ22/98797: пользовательские / клавиши разблокировки'
I/DEBUG   ( 1505): pid: 17641, tid: 17641
I/DEBUG   ( 1505): сигнал 11 (SIGSEGV), код 1 (SEGV_MAPERR), адрес ошибки 00000000
I/DEBUG   ( 1505):  r0 0055dab8  r1 00000000  r2 00000000  r3 0055dad I / Отладка (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  pcs 60011 010 I / ОТЛАДКА ( 1505):  d0  414000003f800000  d1  2073646565637834
I/ ОТЛАДКА ( 1505):  d2  4de4b8bc426fb934  d3  42c80000007a1f34
I/ ОТЛАДКА (1505): d4 00000008004930e0 d5 0000000000000000 I / ОТЛАДКА (1505): d 6 0000000000000000 D7 4080000080000000 I / ОТЛАДКА (1505): d8 0000025843e7c000 D9 c0c0000040c00000 I / ОТЛАДКА (1505): d10 40c0000040c00000 d11 0000000000000000 I / ОТЛАДКА (1505): d12 0000000000000000 D13 0000000000000000 I / ОТЛАДКА (1505): d14 0000000000000000 d15 0000000000000000 I / ОТЛАДКА (1505): d16 afd4242840704ab8 D17 0000000000000000
I/DEBUG   ( 1505):  d18 0000000000000000 d19 0000000000000000
I/DEBUG   ( 1505):  d20 0000000000000000 d21 0000000000000000
I/DEBUG   ( 1505):  d22 00000000000000 d23 0000000000000000 I / DEBUG (1500000000000000000000000000000000000000000000000000000: 1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 по запросу:: d26 0000000000000000 d27 0000000000000000
I/DEBUG   ( 1505):  d28 00ff00ff00ff00ff d29 00ff00ff00ff00ff I / DEBUG   ( 1505):  d30 0000000000000000  d31 3fe55167807de022
I/DEBUG   ( 1505):  scr 680000 

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

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

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

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

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

ОТВЕТ: ограничение для NativeHeapAllocSize

4 ответа

Решение

Используйте этот фрагмент, работал для меня

/**
 * 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;

}

Вот пример использования

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

Итак, я начинаю подозревать, что ограничение в собственном режиме применяется к общему размеру кучи Java + собственная используемая память.

Предел основан на NativeHeapAllocSize против maxMemory(). Ниже вы увидите, что я рушаюсь, выделяя ~1 МБ, а я на 22,0 МБ / 24 МБ. Предел является верхним пределом того, сколько памяти вы можете выделить. Это то, что бросило меня на некоторое время. Авария происходит значительно раньше, чем вы достигнете предела. Таким образом, потребность в значении "memoryPad" в решении, поскольку попытка выделить 23,999 МБ / 24 МБ приведет к сбою почти в 100% случаев. Так что, если лимит составляет 24 МиБ, сколько вы можете безопасно использовать?? Неизвестный. 20 МиБ похоже на работу. 22 МиБ похоже на работу. Я нервничаю, подталкивая ближе, чем это. Количество изменяется в зависимости от того, насколько фрагментировано пространство памяти malloc в собственном процессе. И, конечно, нет способа измерить что-либо из этого, так что ошибайтесь в безопасности.

07-31 18: 37: 19.031: WARN / Epic (3118): ИСПОЛЬЗУЕТСЯ ПАМЯТЬ: 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: ОШИБКА /dalvikvm-куча (3118): 1111200-байтовое внешнее выделение слишком велико для этого процесса. 07-31 18: 37: 19.081: ОШИБКА /dalvikvm(3118): Недостаточно памяти: размер кучи =6535 КБ, выделенный =4247 КБ, размер растрового изображения =17767 КБ 07-31 18: 37: 19.081: ОШИБКА / Графика JNI(3118): ВМ не позволит нам выделить 1111200 байт

Код для распечатки всего этого:


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

Ограничение варьируется для каждого устройства (используйте третью ссылку, если вы хотите загрузить растровое изображение как есть), или здесь у вас есть несколько хитростей, чтобы избежать этой проблемы, например:

  • используйте onLowMemory() класса Application для освобождения некоторой памяти во избежание сбоя.
  • Укажите желаемый размер для растрового изображения перед его декодированием. Проверьте эти ссылки для получения дополнительной информации:

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

Проблема с нехваткой памяти при загрузке изображения в растровый объект

Эту ссылку покажи, чтобы проверить кучу

BitmapFactory OOM сводит меня с ума

  • И, конечно, освободить память о старых растровых изображениях

Если вы поймаете «OutOfMemory», вы можете рассчитать растровую память следующим образом:

      WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getRealSize(size);
// Cap memory usage at 1.5 times the size of the display
// 1.5 * 4 bytes/pixel * w * h ==> 6 * w * h
int mMaxWidgetBitmapMemory = 6 * size.x * size.y;
Log.i(TAG,"mMaxWidgetBitmapMemory result: " + mMaxWidgetBitmapMemory);
Другие вопросы по тегам