系统相关
首页 > 系统相关> > android – Bitmap分配如何在Oreo上运行,以及如何调查它们的内存?

android – Bitmap分配如何在Oreo上运行,以及如何调查它们的内存?

作者:互联网

背景

在过去几年中,为了检查您在Android上有多少堆内存以及您使用了多少内存,您可以使用以下内容:

@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 + "%)"
}

这意味着,您使用的内存越多,特别是通过将位图存储到内存中,您就可以越接近应用程序允许使用的最大堆内存.当您达到最大值时,您的应用程序将因OutOfMemory异常(OOM)而崩溃.

问题

我注意到在Android O上(在我的情况下是8.1,但它也可能在8.0上),上面的代码不受Bitmap分配的影响.

进一步深入,我注意到在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))
            }
        }
    }

在某些情况下,我已经在一次迭代中完成了它,而在某些情况下,我只在列表中创建了一个位图,而不是解码它(注释中的代码).稍后会详细介绍……

这是运行上面的结果:

enter image description here

从图中可以看出,应用程序达到了巨大的内存使用量,远高于报告给我的允许的最大堆内存(即201MB).

我发现了什么

我发现了很多奇怪的行为.因此,我决定报告它们,here.

>首先,我尝试了上述代码的替代方法,以便在运行时获取内存统计信息:

 @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 + "%)"
 }

但是,与堆内存检查相反,似乎最大本机内存会随着时间的推移而改变其值,这意味着我无法知道它的真正最大值是什么,所以我不能在实际应用中决定什么是内存缓存大小应该是.这是上面代码的结果:

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%)

>当我到达设备无法存储更多位图(在我的Nexus 5x上停止在1.1GB或~850MB上)时,而不是OutOfMemory异常,我得到……没有!它只是关闭应用程序.甚至没有对话说它已经崩溃了.
>如果我只是创建一个新的Bitmap,而不是解码它(上面提供的代码,只是在注释中),我得到一个奇怪的日志,说我使用大量的GB并且有大量GB的本机内存可用:

enter image description here

另外,与我解码位图相反,我确实在这里遇到了崩溃(包括对话框),但它不是OOM.相反,它是…… NPE!

01-04 10:12:36.936 30598-31301/com.example.user.myapplication E/AndroidRuntime: FATAL EXCEPTION: AsyncTask #1
Process: com.example.user.myapplication, PID: 30598
java.lang.NullPointerException: Attempt to invoke virtual method ‘void
android.graphics.Bitmap.setHasAlpha(boolean)’ on a null object
reference
at android.graphics.Bitmap.createBitmap(Bitmap.java:1046)
at android.graphics.Bitmap.createBitmap(Bitmap.java:980)
at android.graphics.Bitmap.createBitmap(Bitmap.java:930)
at android.graphics.Bitmap.createBitmap(Bitmap.java:891)
at
com.example.user.myapplication.MainActivity$onCreate$1$1.run(MainActivity.kt:21)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:245)
at
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
at
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
at java.lang.Thread.run(Thread.java:764)

查看分析器图表,它甚至更奇怪.内存使用量似乎没有太大增长,在崩溃点,它只是下降:

enter image description here

如果你看图表,你会看到很多GC图标(垃圾桶).我认为它可能正在做一些内存压缩.

>如果我进行内存转储(使用分析器),而不是以前版本的Android,我再也看不到Bitmaps的预览了.

enter image description here

问题

这种新行为引发了很多问题.它可以减少OOM的崩溃次数,但也可能使它很难检测到它们,发现内存泄漏并修复它们.也许我见过的一些只是错误,但仍然……

> Android O上的内存使用情况究竟发生了哪些变化?为什么?
>如何处理位图?
>是否仍可以在内存转储报告中预览位图?
>获取应用程序允许使用的最大本机内存的正确方法是什么,并将其打印在日志中,并将其用作决定最大值的内容?
>有关于此主题的视频/文章吗?我不是在谈论添加的内存优化,而是更多关于如何分配Bitmaps,如何处理OOM等等…
>我猜这种新行为可能会影响一些缓存库,对吧?那是因为它们可能取决于堆内存大小.
>我怎么可能创建这么多的位图,每个大小为20,000×20,000(意思是~1.6 GB),但是当我只能从7,680×7,680(意味着~236MB)的真实图像中创建一些位图时)?正如我猜测的那样,它真的会进行内存压缩吗?
>在创建位图的情况下,本机存储器函数如何能够返回如此巨大的值,而当我解码位图时,更合理的是这些值?他们的意思是什么?
> Bitmap创建案例中奇怪的分析器图表是什么?它的内存使用量几乎没有增加,但它最终达到了无法创建它们的程度(在插入了大量项目之后).
>奇怪的异常行为是什么?为什么在位图解码中我没有异常甚至错误日志作为应用程序的一部分,当我创建它们时,我得到了NPE?
>如果应用程序崩溃,Play商店是否会检测到OOM并仍然报告它们?它会在所有情况下检测到它吗? Crashlytics可以检测到它吗?有无法通过用户或在办公室开发过程中了解此类事情的方法吗?

解决方法:

看起来你的应用程序被Linux OOM杀手杀死.游戏开发者和其他积极使用本机内存的人看到这种情况一直在发生.

启用内核过量使用以及对位图分配提升基于堆的限制可能会导致您看到的图片.你可以阅读一下有关过度使用here的内容.

就个人而言,我很想看到一个用于了解app死亡的操作系统API,但我不会屏住呼吸.

  1. What’s the correct way to get the max native memory that app is allowed to use, and print it on logs, and use it as something to decide of max ?

选择一些任意值(比如堆大小的四分之一)并坚持下去.如果您调用onTrimMemory(与OOM杀手和本机内存压力直接相关),请尝试减少消耗.

  1. I guess this new behavior might affect some caching libraries, right? That’s because they might depend on the heap memory size instead.

无关紧要 – Android堆大小始终小于总物理内存.使用堆大小作为指导的任何缓存库都应继续以任何方式工作.

  1. How could it be that I could create so many bitmaps, each of size 20,000×20,000

魔法.

我假设,当前版本的Android Oreo允许内存过量使用:实际上并未从硬件请求未触及的内存,因此您可以拥有OS可寻址内存限制所允许的尽可能多的内容(x86上小于2千兆字节,几兆兆字节)在x64).所有virtual memory都包含页面(通常为每个4Kb).当您尝试使用页面时,它会被页面调入.如果内核没有足够的物理内存来映射您的进程的页面,应用程序将收到一个信号,将其终止.在实践中,应用程序将在此之前被Linux OOM杀手方式杀死.

  1. How could the native memory functions return me such huge values in the case of bitmap creation, yet more reasonably ones for when I decoded bitmaps ? What do they mean?

  2. What’s with the weird profiler graph on the Bitmap creation case? It barely rises in memory usage, and yet it reached a point that it can’t create any more of them, eventually (after a lot of items being inserted).

分析器图显示堆内存使用情况.如果位图不计数
对于堆,该图自然不会显示它们.

本机内存函数看起来像(最初)预期的那样 – 它们正确地跟踪虚拟分配,但没有意识到内核为每个虚拟分配保留了多少物理内存(对用户空间不透明).

Also, as opposed to when I decode bitmaps, I do get a crash here (including a dialog), but it’s not OOM. Instead, it’s… NPE !

您没有使用过任何这些页面,因此它们没有映射到物理内存,因此OOM杀手不会杀死你(还).分配可能已经失败,因为你的虚拟内存耗尽,与物理内存耗尽相比更无害,或者因为遇到其他类型的内存限制(例如基于cgroups的内存限制),这甚至更多无害.

  1. …Can Crashlytics detect it? Is there a way to be informed of such a thing, whether by users or during development at the office?

OOM杀手用SIGKILL破坏你的应用程序(与你的进程在进入后台后终止时相同).您的流程无法对其做出反应.理论上可以从儿童过程中观察过程死亡,但确切的原因可能很难学习.请参阅Who “Killed” my process and why?.编写良好的库可能能够定期检查内存使用情况并进行有根据的猜测.一个编写得非常好的库可能能够通过挂钩到本机malloc函数来检测内存分配(例如,通过热修补应用程序导入表或类似的东西).

为了更好地演示虚拟内存管理的工作原理,让我们假设每个分配1000个位图1Gb,然后在每个位图中更改一个像素.操作系统最初不为这些位图分配物理内存,因此它们总共需要大约0字节的物理内存.触摸Bitmap的单个四字节RGBA像素后,内核将分配一个页面来存储该像素.

操作系统对Java对象和位图一无所知 – 它只是将所有进程内存视为连续的页面列表.

常用的内存页面大小为4Kb.触摸1000像素(每个1Gb位图中有一个)后,您仍然会消耗不到4Mb的实内存.

标签:android-8-0-oreo,android,memory,bitmap
来源: https://codeday.me/bug/20190923/1815250.html