31

I've been doing a lot of searching and I know a lot of other people are experiencing the same OOM memory problems with BitmapFactory. My app only shows a total memory available of 4MB using Runtime.getRuntime ().totalMemory(). If the limit is 16MB, then why doesn't the total memory grow to make room for the bitmap? Instead it throws an error.

I also don't understand that if I have 1.6MB of free memory according to Runtime.getRuntime().freeMemory() why do I get an error saying "VM won't let us allocate 614400 bytes"? Seems to me I have plenty available memory.

My app is complete except for this problem, which goes away when I reboot the phone so that my app is the only thing running. I'm using an HTC Hero for device testing (Android 1.5).

At this point I'm thinking the only way around this is to somehow avoid using BitmapFactory.

Anyone have any ideas on this or an explanation as to why VM won't allocate 614KB when there's 1.6MB of free memory?

4

5 回答 5

47

[请注意(正如 CommonsWare 在下面指出的那样)此答案中的整个方法仅适用于并包括 2.3.x(姜饼)。从 Honeycomb 开始,位图数据在 VM 堆中分配。]

位图数据不在 VM 堆中分配。在VM heap(很小)中有对其的引用,但实际数据是由底层Skia图形库在Native heap中分配的。

不幸的是,虽然 BitmapFactory.decode...() 的定义说如果无法解码图像数据,它会返回 null,但 Skia 实现(或者更确切地说是 Java 代码和 Skia 之间的 JNI 胶水)记录了您的消息看到(“VM 不会让我们分配 xxxx 字节”),然后引发 OutOfMemory 异常,并带有误导性消息“位图大小超出 VM 预算”。

问题不在 VM 堆中,而是在本机堆中。Natïve 堆在正在运行的应用程序之间共享,因此可用空间量取决于正在运行的其他应用程序及其位图使用情况。但是,鉴于 BitmapFactory 不会返回,您需要一种方法在调用之前确定调用是否会成功。

有一些例程可以监视 Native 堆的大小(请参阅 Debug 类的 getNative 方法)。但是,我发现 getNativeHeapFreeSize() 和 getNativeHeapSize() 并不可靠。因此,在我的一个动态创建大量位图的应用程序中,我执行以下操作。

本机堆大小因平台而异。因此,在启动时,我们检查允许的最大 VM 堆大小以确定允许的最大 Native 堆大小。[幻数是通过对 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(即 3Mbytes)的 pad 运行。

于 2011-03-30T22:23:08.423 回答
2

1.6 MB 的内存看起来很多,但可能是内存碎片严重以至于无法一次性分配这么大的内存块(这听起来很奇怪)。

使用图像资源时出现 OOM 的一个常见原因是在解压缩具有真正高分辨率的 JPG、PNG、GIF 图像时。您需要记住,所有这些格式都经过很好的压缩并且占用的空间非常小,但是一旦您将图像加载到手机中,它们将使用的内存类似于width * height * 4 bytes. 此外,当解压缩开始时,需要为解码步骤加载一些其他辅助数据结构。

于 2009-12-24T13:45:25.780 回答
2

似乎Torid 的答案中给出的问题已在最新版本的 Android 中得到解决。

但是,如果您使用的是图像缓存(专用缓存,甚至只是普通的 HashMap),则很容易通过创建内存泄漏来获取此错误。

根据我的经验,如果您无意中保留了Bitmap引用并造成了内存泄漏,那么 OP 的错误(指的是BitmapFactory和本机方法)将会导致您的应用程序崩溃(直到 ICS - 14 和 +?)

为了避免这种情况,让你“放手”你的位图。这意味着在缓存的最后一层使用软引用,以便位图可以从中收集垃圾。这应该可以,但是如果您仍然遇到崩溃,您可以尝试使用 显式标记某些位图以供收集bitmap.recycle(),只要记住永远不要返回位图以在您的应用程序中使用bitmap.isRecycled()

顺便说一句,LinkedHashMaps是一个很好的工具,可以轻松实现非常好的缓存结构,特别是如果你像这个例子中那样结合硬引用和软引用(从第 308 行开始) ......但是使用硬引用也是你进入内存的方法如果你搞砸了泄漏情况。

于 2012-04-01T19:44:13.837 回答
0

尽管这是一个相当高级的答案,但对我来说,问题原来是在我的所有视图中都使用了硬件加速。我的大多数视图都有自定义位图操作,我认为这是大本机堆大小的来源,但实际上当禁用硬件加速时,本机堆使用量减少了 4 倍。

似乎硬件加速会对您的视图进行各种缓存,创建自己的位图,并且由于所有位图共享本机堆,分配大小可以显着增长。

于 2011-12-01T22:28:54.087 回答
0

虽然通常捕获错误是没有意义的,因为它们通常只由 vm 抛出,但在这种特殊情况下,错误是由 jni 胶水代码抛出的,因此处理无法加载图像的情况非常简单:只需捕获 OutOfMemoryError。

于 2011-07-18T20:51:22.433 回答