BitmapFactory OOM 让我抓狂

发布于 2024-08-16 03:44:28 字数 487 浏览 5 评论 0 原文

我做了很多搜索并且认识了很多人 BitmapFactory 也遇到同样的 OOM 内存问题。我的 使用 Runtime.getRuntime 应用程序仅显示 4MB 的可用总内存 ().totalMemory()。如果限制是 16MB,那么为什么总数不 内存增长以便为位图腾出空间?相反,它会抛出错误。

我也不明白如果我有 1.6MB 的可用内存 到 Runtime.getRuntime().freeMemory() 为什么我会收到错误消息“VM 不会让我们分配 614400 字节”?在我看来我有足够的 可用内存。

我的应用程序是完整的,除了这个问题,当我 重新启动手机,以便我的应用程序是唯一运行的。我正在使用 用于设备测试的 HTC Hero (Android 1.5)。

在这一点上,我认为解决这个问题的唯一方法就是以某种方式 避免使用 BitmapFactory。

任何人对此有任何想法或解释为什么 VM 不会 当有 1.6MB 空闲内存时分配 614KB?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

明媚如初 2024-08-23 03:44:28

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

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

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

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

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

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

[Note that (as CommonsWare points out below) the whole approach in this answer only applies up to and including 2.3.x (Gingerbread). As of Honeycomb Bitmap data is allocated in the VM heap.]

Bitmap data is not allocated in the VM heap. There is a reference to it in the VM heap (which is small), but the actual data is allocated in the Native heap by the underlying Skia graphics library.

Unfortunately, while the definition of BitmapFactory.decode...() says that it returns null if the image data could not be decoded, the Skia implementation (or rather the JNI glue between the Java code and Skia) logs the message you’re seeing ("VM won't let us allocate xxxx bytes") and then throws an OutOfMemory exception with the misleading message "bitmap size exceeds VM budget".

The issue is not in the VM heap but is rather in the Native heap. The Natïve heap is shared between running applications, so the amount of free space depends on what other applications are running and their bitmap usage. But, given that BitmapFactory will not return, you need a way to figure out if the call is going to succeed before you make it.

There are routines to monitor the size of the Native heap (see the Debug class getNative methods). However, I have found that getNativeHeapFreeSize() and getNativeHeapSize() are not reliable. So in one of my applications that dynamically creates a large number of bitmaps I do the following.

The Native heap size varies by platform. So at startup, we check the maximum allowed VM heap size to determine the maximum allowed Native heap size. [The magic numbers were determined by testing on 2.1 and 2.2, and may be different on other API levels.]

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

Then each time we need to call BitmapFactory we precede the call by a check of the form.

long sizeReqd        = bitmapWidth * bitmapHeight * targetBpp  / 8;
long allocNativeHeap = Debug.getNativeHeapAllocatedSize();
if ((sizeReqd + allocNativeHeap + heapPad) >= mMaxNativeHeap)
{
    // Do not call BitmapFactory…
}

Note that the heapPad is a magic number to allow for the fact that a) the reporting of Native heap size is "soft" and b) we want to leave some space in the Native heap for other applications. We are running with a 3*1024*1024 (ie 3Mbytes) pad currently.

缘字诀 2024-08-23 03:44:28

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

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

1.6 MB of memory seems like a lot but it could be the case that the memory is so badly fragmented that it can't allocate such big block of memory in one go (still this does sound very strange).

One common cause of OOM while using image resources is when one is decompressing JPG, PNG, GIF images with really high resolutions. You need to bear in mind that all these formats are pretty well compressed and take up very little space but once you load the images to the phone, the memory they're going to use is something like width * height * 4 bytes. Also, when decompression kicks in, a few other auxiliary data structures need to be loaded for the decoding step.

七婞 2024-08-23 03:44:28

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

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

根据我的经验,如果您无意中保留了您的位图引用并创建内存泄漏,OP的错误(参考BitmapFactory 和本机方法)会导致您的应用程序崩溃(最高可达 ICS - 14 和 +?)

为了避免这种情况,请让您“放开”您的位图。这意味着在缓存的最后一层使用软引用,以便位图可以从中收集垃圾。这应该可行,但如果您仍然遇到崩溃,您可以尝试使用 bitmap.recycle(),请记住,如果 < a href="http://developer.android.com/reference/android/graphics/Bitmap.html#isRecycled%28%29" rel="nofollow noreferrer">bitmap.isRecycled()

顺便说一句,LinkedHashMaps 是一个很好的工具,可以轻松实现相当好的缓存结构,特别是当您像 这个示例(从第 308 行开始)...但是使用硬如果你搞砸了,引用也是让你陷入内存泄漏情况的方法。

It seems like the issues given in Torid's answer have been resolved in the more recent versions of Android.

However, if you are using an image cache (a specialized one or even just a regular HashMap), it is pretty easy to get this error by creating a memory leak.

In my experience, if you inadvertently hold on to your Bitmap references and create a memory leak, OP's error (an referring to the BitmapFactory and native methods) is the one that will crash your app (up to ICS - 14 and +?)

To avoid this, make your you "let go" of your Bitmaps. This means using SoftReferences in the final tier of your cache, so that Bitmaps can get garbage collected out of it. This should work, but if you are still getting crashes, you can try to explicitly mark certain Bitmaps for collection by using bitmap.recycle(), just remember to never return a bitmap for use in your app if bitmap.isRecycled().

As an aside, LinkedHashMaps are a great tool for easily implementing pretty good cache structures, especially if you combine hard and soft references like in this example (starting line 308)... but using hard references is also how you can get yourself into memory leak situations if you mess up.

耶耶耶 2024-08-23 03:44:28

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

Although usually it doesnt make sense to catch an Error because usually they are thrown only by the vm but in this particular case the Error is thrown by the jni glue code thus it is very simple to handle cases where you could not load the image: just catch the OutOfMemoryError.

过去的过去 2024-08-23 03:44:28

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

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

Although this is a fairly high level answer, the problem for me turned out to be using hardware acceleration on all of my views. Most of my views have custom Bitmap manipulation, which I figured to be the source of the large native heap size, but in fact when disabling hardware acceleration the native heap usage was cut down by a factor of 4.

It seems as though hardware acceleration will do all kinds of caching on your views, creating bitmaps of its own, and since all bitmaps share the native heap, the allocation size can grow pretty dramatically.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文