gpt4 book ai didi

android - 位图分配如何在奥利奥上工作,以及如何调查它们的内存?

转载 作者:行者123 更新时间:2023-12-03 14:22:46 26 4
gpt4 key购买 nike

背景

在过去的几年里,为了检查你在 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 上),上面的代码不受位图分配的影响。

进一步挖掘,我在 Android 分析器中注意到您使用的内存越多(在我的 POC 中保存大位图),使用的 native 内存就越多。

为了测试它是如何工作的,我创建了一个简单的循环:
    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 + "%)"
    }

  • 但是,与堆内存检查相反,最大 native 内存似乎随着时间的推移而改变其值,这意味着我无法知道它真正的最大值是多少,因此我无法在实际应用程序中决定什么是内存缓存大小应该是。下面是上面代码的结果:
    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 异常,我得到......什么都没有!它只是关闭应用程序。甚至没有一个对话框说它已经崩溃了。
  • 如果我只是创建一个新的位图,而不是解码它(上面提供的代码,只是在评论中),我会收到一个奇怪的日志,说我使用了大量 GB 并且有大量可用的 native 内存:

  • 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 不同,我就看不到位图的预览了。

  • enter image description here

    问题

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

    看起来您的应用程序被 Linux OOM 杀手杀死了。积极使用 native 内存的游戏开发人员和其他人一直看到这种情况发生。
    启用内核过量使用以及解除对位图分配的基于堆的限制可能会导致您看到的图片。您可以阅读一些关于过量使用的内容 here .
    就我个人而言,我很想看到一个用于了解应用程序死亡的操作系统 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杀手和 native 内存压力直接相关),尽量减少你的消耗。
    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,000x20,000

    魔法。
    我认为,当前版本的 Android Oreo 允许内存过量使用:实际上并未从硬件请求未触及的内存,因此您可以拥有操作系统可寻址内存限制所允许的尽可能多的内存(在 x86 上小于 2 GB,几 TB在 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).


    分析器图显示堆内存使用情况。如果位图不算数
    对于堆,该图自然不会显示它们。
    native 内存函数似乎按(最初)预期工作——它们正确跟踪虚拟分配,但没有意识到内核为每个虚拟分配保留了多少物理内存(对用户空间不透明)。

    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 破坏您的应用程序(与您的进程在进入后台后终止时相同)。您的过程无法对其使用react。理论上可以从子进程观察到进程死亡,但确切的原因可能很难了解。见 Who “Killed” my process and why? .一个编写良好的库可能能够定期检查内存使用情况并做出有根据的猜测。一个写得非常好的库可能能够通过 Hook 到 native malloc 来检测内存分配。功能(例如,通过热修补应用程序导入表或类似的东西)。

    为了更好地演示虚拟内存管理的工作原理,让我们想象一下分配 1000 个 1Gb 的位图,然后更改每个中的一个像素。操作系统最初不会为这些位图分配物理内存,因此它们总共占用大约 0 字节的物理内存。在触摸 Bitmap 的单个四字节 RGBA 像素后,内核将分配一个页面来存储该像素。
    操作系统对 Java 对象和位图一无所知——它只是将所有进程内存视为连续的页面列表。
    常用的内存页大小为4Kb。触摸 1000 个像素(每个 1Gb 位图中一个)后,您仍将使用不到 4Mb 的实际内存。

    关于android - 位图分配如何在奥利奥上工作,以及如何调查它们的内存?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48091403/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com