BitmapFactory OOM заставляет меня гайки

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

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

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

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

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

Ответ 1

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

Данные битмапа не выделены в куче VM. Существует ссылка на него в куче VM (что мало), но фактические данные выделяются в Native heap базовой графической библиотекой Skia.

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

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

Существуют подпрограммы для контроля размера нативной кучи (см. методы 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) отчет о размере кучи нации является "мягким" и b) мы хотим оставить некоторое пространство в "Нативной куче" для других приложений. В настоящее время мы работаем с 3 * 1024 * 1024 (т.е. 3 Мбайт).

Ответ 2

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

Одной из распространенных причин OOM при использовании ресурсов изображения является разложение JPG, PNG, GIF изображений с действительно высоким разрешением. Вы должны иметь в виду, что все эти форматы довольно хорошо сжаты и занимают очень мало места, но как только вы загружаете изображения на телефон, память, которую они собираются использовать, похожа на width * height * 4 bytes. Кроме того, при распаковке декомпрессии необходимо загрузить несколько дополнительных структур данных для этапа декодирования.

Ответ 3

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

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

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

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

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

Ответ 4

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

Ответ 5

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

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