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

Я много занимался поиском, и я знаю, что многие другие люди испытывают те же проблемы с памятью OOM с BitmapFactory, Мое приложение показывает только общую доступную память 4 МБ, используя Runtime.getRuntime ().totalMemory(), Если ограничение составляет 16 МБ, то почему общий объем памяти не увеличивается, чтобы освободить место для растрового изображения? Вместо этого он выдает ошибку.

Я также не понимаю, что если у меня есть 1,6 МБ свободной памяти в соответствии с Runtime.getRuntime().freeMemory() почему я получаю сообщение об ошибке "VM не позволяет выделять 614400 байт"? Сдается мне, у меня много свободной памяти.

Мое приложение завершено, за исключением этой проблемы, которая исчезает, когда я перезагружаю телефон, так что мое приложение работает единственно. Я использую HTC Hero для тестирования устройств (Android 1.5).

На данный момент я думаю, что единственный способ обойти это как-то избежать использования BitmapFactory,

У кого-нибудь есть идеи или объяснения, почему VM не будет выделять 614 КБ при наличии 1,6 МБ свободной памяти?

5 ответов

[Обратите внимание, что (как указывает CommonsWare ниже) весь подход в этом ответе применим только до 2.3.x включительно (Gingerbread). По состоянию на Honeycomb Bitmap данные размещаются в куче виртуальных машин.]

Растровые данные не размещаются в куче виртуальных машин. Существует ссылка на него в куче виртуальной машины (которая мала), но фактические данные выделяются в куче собственной памяти базовой графической библиотекой Skia.

К сожалению, хотя определение BitmapFactory.decode...() говорит, что он возвращает ноль, если данные изображения не могут быть декодированы, реализация Skia (или, скорее, клей JNI между кодом Java и Skia) регистрирует сообщение, которое вы видя ("VM не позволяет нам выделять байты xxxx"), а затем выдает исключение OutOfMemory с вводящим в заблуждение сообщением "размер растрового изображения превышает бюджет VM".

Проблема не в куче виртуальных машин, а в собственной куче. Естественная куча распределяется между запущенными приложениями, поэтому объем свободного места зависит от того, какие другие приложения работают, и от их использования растрового изображения. Но, учитывая, что BitmapFactory не вернется, вам нужен способ выяснить, будет ли вызов успешным, прежде чем вы его сделаете.

Существуют подпрограммы для контроля размера кучи Native (см. Методы getNative класса Debug). Однако я обнаружил, что getNativeHeapFreeSize() и getNativeHeapSize() не являются надежными. Поэтому в одном из моих приложений, которое динамически создает большое количество растровых изображений, я делаю следующее.

Размер собственной кучи зависит от платформы. Поэтому при запуске мы проверяем максимально допустимый размер кучи виртуальной машины, чтобы определить максимально допустимый собственный размер кучи. [Магические числа были определены при тестировании на 2.1 и 2.2, и могут отличаться на других уровнях API.]

long mMaxVmHeap     = Runtime.getRuntime().maxMemory()/1024;
long mMaxNativeHeap = 16*1024;
if (mMaxVmHeap == 16*1024)
     mMaxNativeHeap = 16*1024;
else if (mMaxVmHeap == 24*1024)
     mMaxNativeHeap = 24*1024;
else
    Log.w(TAG, "Unrecognized VM heap size = " + mMaxVmHeap);

Затем каждый раз, когда нам нужно вызвать BitmapFactory, мы предшествуем вызову проверкой формы.

long sizeReqd        = bitmapWidth * bitmapHeight * targetBpp  / 8;
long allocNativeHeap = Debug.getNativeHeapAllocatedSize();
if ((sizeReqd + allocNativeHeap + heapPad) >= mMaxNativeHeap)
{
    // Do not call BitmapFactory…
}

Обратите внимание, что heapPad - это магическое число, учитывающее тот факт, что a) отчет о размере кучи Native является "мягким", и b) мы хотим оставить некоторое место в куче Native для других приложений. В настоящее время мы работаем с пэдом 3*1024*1024 (т.е. 3 Мб).

1,6 МБ памяти кажется большим, но может случиться так, что память настолько сильно фрагментирована, что не сможет выделить такой большой блок памяти за один раз (все же это звучит очень странно).

Одной из распространенных причин использования OOM при использовании ресурсов изображений является распаковка изображений JPG, PNG, GIF с действительно высоким разрешением. You need to bear in mind that all these formats are pretty well compressed and take up very little space but once you load the images to the phone, the memory they're going to use is something like width * height * 4 bytes, Also, when decompression kicks in, a few other auxiliary data structures need to be loaded for the decoding step.

Похоже, что проблемы, указанные в ответе Торида, были решены в более поздних версиях Android.

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

По моему опыту, если вы непреднамеренно держитесь за ссылки на растровые изображения и создаете утечку памяти, ошибка OP (ссылающаяся на BitmapFactory и нативные методы) будет причиной сбоя вашего приложения (до ICS - 14 и +?)

Чтобы избежать этого, заставьте вас "отпустить" свои растровые изображения. Это означает использование SoftReferences на последнем уровне вашего кеша, чтобы растровые изображения могли собирать мусор из него. Это должно работать, но если вы все еще получаете сбои, вы можете попытаться явно пометить определенные битовые карты для сбора с помощью bitmap.recycle() просто не забывайте никогда не возвращать растровое изображение для использования в вашем приложении, если bitmap.isRecycled(),

Кроме того, LinkedHashMaps - отличный инструмент для простой реализации довольно хороших структур кэша, особенно если вы комбинируете жесткие и мягкие ссылки, как в этом примере (строка 308)... но использование жестких ссылок также помогает вам попасть в память утечка ситуаций, если вы запутались.

Хотя обычно нет смысла отлавливать ошибку, потому что обычно она выдается только виртуальной машиной, но в данном конкретном случае ошибка генерируется кодом jni glue, поэтому очень просто обрабатывать случаи, когда вы не можете загрузить изображение: просто поймать OutOfMemoryError.

Хотя это ответ довольно высокого уровня, проблемой для меня оказалось использование аппаратного ускорения во всех моих представлениях. Большинство моих представлений имеют пользовательские манипуляции с растровыми изображениями, которые, как я предполагал, являются источником большого размера собственной кучи, но фактически при отключении аппаратного ускорения использование собственной кучи было сокращено в 4 раза.

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

Другие вопросы по тегам