«В прошлом, например, было возможно использовать try-catch на случай, если вы декодируете огромный битовый массив, который может вызвать OOM» - я думаю, это недостаток текущей реализации. Теоретически, поскольку хранилище растровых изображений не подвергается непосредственному воздействию Java-кода, должна быть возможность выполнять учетные проверки по требованию в коде, который обращается к растровому памяти. Эти проверки, в свою очередь, могут выбирать, выдавать OOM Error или нет. Обратите внимание, что сверхкоммит всегда позволяет выстрелить себе в ногу с помощью JNI - эти изменения в растровом изображении делают это проще для людей, которые не используют JNI.

последние годы, чтобы проверить, сколько памяти у вас на Android и сколько вы используете, вы можете использовать что-то вроде:

Это означает, что чем больше памяти вы используете, особенно путем хранения растровых изображений в памяти, тем ближе вы получаете максимальный объем кучи памяти, которую может использовать ваше приложение. Когда вы достигнете максимума, ваше приложение будет аварийно завершено с исключением OutOfMemory (OOM).

@JvmStatic
fun getHeapMemStats(context: Context): String {
    val runtime = Runtime.getRuntime()
    val maxMemInBytes = runtime.maxMemory()
    val availableMemInBytes = runtime.maxMemory() - (runtime.totalMemory() - runtime.freeMemory())
    val usedMemInBytes = maxMemInBytes - availableMemInBytes
    val usedMemInPercentage = usedMemInBytes * 100 / maxMemInBytes
    return "used: " + Formatter.formatShortFileSize(context, usedMemInBytes) + " / " +
            Formatter.formatShortFileSize(context, maxMemInBytes) + " (" + usedMemInPercentage + "%)"
}

Проблема

Я заметил, что на Android O (в моем случае это 8.1, но, вероятно, и на 8.0), вышеприведенный код не подвержен распределению растровых изображений.

Продолжая копаться, я заметил в профилировщике Android, что чем больше памяти вы используете (сохраняя большие растровые изображения в моем POC), тем больше используемой памяти.

Чтобы проверить, как это работает, я создал простой цикл как таковой:

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

    val list = ArrayList<Bitmap>()
    Log.d("AppLog", "memStats:" + MemHelper.getHeapMemStats(this))
    useMoreMemoryButton.setOnClickListener {
        AsyncTask.execute {
            for (i in 0..1000) {
                // list.add(Bitmap.createBitmap(20000, 20000, Bitmap.Config.ARGB_8888))
                list.add(BitmapFactory.decodeResource(resources, R.drawable.huge_image))
                Log.d("AppLog", "heapMemStats:" + MemHelper.getHeapMemStats(this) + " nativeMemStats:" + MemHelper.getNativeMemStats(this))
            }
        }
    }

Это результат запуска выше:

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

Что я нашел

Я обнаружил много странного поведения. Из-за этого я решил сообщить о них,

ВотСначала я попробовал альтернативу приведенному выше коду, чтобы получить статистику памяти во время выполнения:.

Но, в отличие от проверки кучи памяти, кажется, что максимальная собственная память со временем меняет свое значение, что означает, что я не могу знать, каково ее истинное максимальное значение, и поэтому в реальных приложениях я не могу решить, что размер кеша памяти должен быть. Вот результат кода выше:

 @JvmStatic
 fun getNativeMemStats(context: Context): String {
     val nativeHeapSize = Debug.getNativeHeapSize()
     val nativeHeapFreeSize = Debug.getNativeHeapFreeSize()
     val usedMemInBytes = nativeHeapSize - nativeHeapFreeSize
     val usedMemInPercentage = usedMemInBytes * 100 / nativeHeapSize
     return "used: " + Formatter.formatShortFileSize(context, usedMemInBytes) + " / " +
             Formatter.formatShortFileSize(context, nativeHeapSize) + " (" + usedMemInPercentage + "%)"
 }

Когда я дохожу до того, что устройство не может хранить больше растровых изображений (остановлено на 1,1 ГБ или ~ 850 МБ на моем Nexus 5x), вместо исключения OutOfMemory я получаю ... ничего! Он просто закрывает приложение. Даже без диалога, говорящего, что это потерпело крах.

heapMemStats:used: 2.0 MB / 201 MB (0%) nativeMemStats:used: 3.6 MB / 6.3 MB (57%)
heapMemStats:used: 1.8 MB / 201 MB (0%) nativeMemStats:used: 290 MB / 310 MB (93%)
heapMemStats:used: 1.8 MB / 201 MB (0%) nativeMemStats:used: 553 MB / 579 MB (95%)
heapMemStats:used: 1.8 MB / 201 MB (0%) nativeMemStats:used: 821 MB / 847 MB (96%)

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

Кроме того, в отличие от того, когда я декодирую растровые изображения, я получаю сбой здесь (включая диалог), но это не OOM. Вместо этого это ... NPE!

01-04 10: 12: 36.936 30598-31301 / com.example.user.myapplication E / AndroidRuntime: ОСНОВНОЕ ИСКЛЮЧЕНИЕ: AsyncTask # 1 Процесс: com.example.user.myapplication, PID: 30598 java.lang.NullPointerException: попытка вызвать виртуальный метод 'void android.graphics.Bitmap.setHasAlpha (boolean)' для пустой ссылки на объект на android.graphics.Bitmap.createBitmap (Bitmap.java:1046) на android.graphics.Bitmap.createBitmap (Bitmap.java:980) ) на android.graphics.Bitmap.createBitmap (Bitmap.java:930) на android.graphics.Bitmap.createBitmap (Bitmap.java:891) на com.example.user.myapplication.MainActivity $ onCreate $ 1 $ 1.run (MainActivity. kt: 21) в android.os.AsyncTask $ SerialExecutor $ 1.run (AsyncTask.java:245) в java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1162) в java.util.concurer.ecread $. выполнить (ThreadPoolExecutor.java:636) в java.lang.Thread.run (Thread.java:764)

Глядя на график профилировщика, он становится еще более странным. Использование памяти, кажется, совсем не увеличивается, а в момент сбоя оно просто падает:

Если вы посмотрите на график, вы увидите много значков GC (мусорное ведро). Я думаю, что это может быть сжатие памяти.

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

Вопросы

Это новое поведение вызывает много вопросов. Это может уменьшить количество сбоев OOM, но также может затруднить их обнаружение, обнаружение утечек памяти и их устранение. Может быть, некоторые из того, что я видел, это просто ошибки, но все же ...

Что именно изменилось в использовании памяти на Android O? И почему?

Как растровые изображения обрабатываются?

Можно ли по-прежнему просматривать растровые изображения в отчетах о дампе памяти?

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

Есть ли видео / статья на эту тему? Я не говорю об оптимизации памяти, которая была добавлена, но больше о том, как распределяются растровые изображения сейчас, как обрабатывать OOM сейчас и т. Д ...

Я предполагаю, что это новое поведение может повлиять на некоторые библиотеки кэширования, верно? Это потому, что они могут зависеть от размера кучи памяти.

Как могло быть так, что я мог создать так много растровых изображений, каждый размером 20 000 x 20 000 (что означает ~ 1,6 ГБ), но когда я мог создать только несколько из них из реального изображения размером 7 680 x 7 680 (то есть ~ 236 МБ) ? Это действительно делает сжатие памяти, как я уже догадался?

Как собственные функции памяти могут вернуть мне такие огромные значения в случае создания растрового изображения, но более разумные, когда я декодировал растровые изображения? Что они имеют в виду?

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

Что за странное поведение исключений? Почему при растровом декодировании я не получил никаких исключений или даже журнал ошибок как часть приложения, и когда я их создал, я получил NPE?

Будет ли Play Store обнаруживать OOM и сообщать о них в случае сбоя приложения из-за этого? Это обнаружит это во всех случаях? Могут ли Crashlytics это обнаружить? Есть ли способ получить информацию о такой вещи, будь то пользователи или во время разработки в офисе?

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

Ответы на вопрос(1)

Ваш ответ на вопрос