尽管有可用内存,但出现 OutOfMemory 错误

发布于 2024-12-10 11:00:34 字数 2295 浏览 4 评论 0原文

我看到一个很奇怪的问题。本质上,有时即使有大量内存,大位图内存分配也会失败。有很多帖子似乎都在问类似的问题,但它们都与前蜂窝 Android 有关。我的理解是图像现在分配在堆上,而不是一些外部内存。无论如何,请查看下面的日志:

    10-14 13:43:53.020: INFO/dalvikvm-heap(31533): Grow heap (frag case) to 40.637MB for 942134-byte allocation
    10-14 13:43:53.070: DEBUG/dalvikvm(31533): GC_FOR_ALLOC freed 126K, 11% free 41399K/46343K, paused 31ms
    10-14 13:43:53.130: DEBUG/dalvikvm(31533): GC_FOR_ALLOC freed 920K, 13% free 40478K/46343K, paused 30ms
    10-14 13:43:53.180: DEBUG/dalvikvm(31533): GC_FOR_ALLOC freed 1026K, 13% free 40479K/46343K, paused 30ms
    10-14 13:43:53.250: DEBUG/dalvikvm(31533): GC_FOR_ALLOC freed 931K, 12% free 41193K/46343K, paused 31ms
    10-14 13:43:53.250: INFO/dalvikvm-heap(31533): Grow heap (frag case) to 41.313MB for 1048592-byte allocation
    10-14 13:43:53.280: DEBUG/dalvikvm(31533): GC_FOR_ALLOC freed <1K, 11% free 42217K/47431K, paused 31ms
    10-14 13:44:01.520: DEBUG/dalvikvm(31533): GC_CONCURRENT freed 3493K, 15% free 40646K/47431K, paused 3ms+9ms
    10-14 13:44:08.130: DEBUG/dalvikvm(31533): GC_EXPLICIT freed 16461K, 47% free 25527K/47431K, paused 3ms+6ms
    10-14 13:44:09.150: DEBUG/dalvikvm(31533): GC_FOR_ALLOC freed 1007K, 45% free 26191K/47431K, paused 35ms
    10-14 13:44:09.160: INFO/dalvikvm-heap(31533): Grow heap (frag case) to 29.334MB for 3850256-byte allocation
    10-14 13:44:09.200: DEBUG/dalvikvm(31533): GC_CONCURRENT freed 0K, 37% free 29951K/47431K, paused 2ms+4ms
    10-14 13:44:11.970: DEBUG/dalvikvm(31533): GC_FOR_ALLOC freed 1878K, 38% free 29784K/47431K, paused 37ms
    10-14 13:44:12.410: DEBUG/dalvikvm(31533): GC_FOR_ALLOC freed 62K, 36% free 30441K/47431K, paused 32ms
    10-14 13:44:12.440: DEBUG/dalvikvm(31533): GC_FOR_ALLOC freed <1K, 32% free 32325K/47431K, paused 32ms
    10-14 13:44:12.440: INFO/dalvikvm-heap(31533): Forcing collection of SoftReferences for 3850256-byte allocation
    10-14 13:44:12.480: DEBUG/dalvikvm(31533): GC_BEFORE_OOM freed 124K, 33% free 32200K/47431K, paused 37ms
    10-14 13:44:12.480: ERROR/dalvikvm-heap(31533): Out of memory on a 3850256-byte allocation.

我很抱歉包含这么多日志记录,我希望它是相关的。我的理解是系统不断地重新调整堆大小,直到最终达到堆最大值。然后,我们请求一个特别大的分配,但失败了。显然有足够的可用内存(大约 15 兆)。这是否意味着堆内部存在碎片并且没有足够大的连续内存段来处理我们的分配?如果是这样的话我该怎么办?如果不是这样,那又怎样呢?

提前致谢。

I'm seeing a pretty odd problem. Essentially sometimes large bitmap memory allocations will fail even though there's apparently tons of memory. There are a number of posts that appear to ask a similar question but they are all related to pre-honeycomb android. My understanding is that images are allocated on heap now, instead of some outside memory. Anyway, please look at this log below:

    10-14 13:43:53.020: INFO/dalvikvm-heap(31533): Grow heap (frag case) to 40.637MB for 942134-byte allocation
    10-14 13:43:53.070: DEBUG/dalvikvm(31533): GC_FOR_ALLOC freed 126K, 11% free 41399K/46343K, paused 31ms
    10-14 13:43:53.130: DEBUG/dalvikvm(31533): GC_FOR_ALLOC freed 920K, 13% free 40478K/46343K, paused 30ms
    10-14 13:43:53.180: DEBUG/dalvikvm(31533): GC_FOR_ALLOC freed 1026K, 13% free 40479K/46343K, paused 30ms
    10-14 13:43:53.250: DEBUG/dalvikvm(31533): GC_FOR_ALLOC freed 931K, 12% free 41193K/46343K, paused 31ms
    10-14 13:43:53.250: INFO/dalvikvm-heap(31533): Grow heap (frag case) to 41.313MB for 1048592-byte allocation
    10-14 13:43:53.280: DEBUG/dalvikvm(31533): GC_FOR_ALLOC freed <1K, 11% free 42217K/47431K, paused 31ms
    10-14 13:44:01.520: DEBUG/dalvikvm(31533): GC_CONCURRENT freed 3493K, 15% free 40646K/47431K, paused 3ms+9ms
    10-14 13:44:08.130: DEBUG/dalvikvm(31533): GC_EXPLICIT freed 16461K, 47% free 25527K/47431K, paused 3ms+6ms
    10-14 13:44:09.150: DEBUG/dalvikvm(31533): GC_FOR_ALLOC freed 1007K, 45% free 26191K/47431K, paused 35ms
    10-14 13:44:09.160: INFO/dalvikvm-heap(31533): Grow heap (frag case) to 29.334MB for 3850256-byte allocation
    10-14 13:44:09.200: DEBUG/dalvikvm(31533): GC_CONCURRENT freed 0K, 37% free 29951K/47431K, paused 2ms+4ms
    10-14 13:44:11.970: DEBUG/dalvikvm(31533): GC_FOR_ALLOC freed 1878K, 38% free 29784K/47431K, paused 37ms
    10-14 13:44:12.410: DEBUG/dalvikvm(31533): GC_FOR_ALLOC freed 62K, 36% free 30441K/47431K, paused 32ms
    10-14 13:44:12.440: DEBUG/dalvikvm(31533): GC_FOR_ALLOC freed <1K, 32% free 32325K/47431K, paused 32ms
    10-14 13:44:12.440: INFO/dalvikvm-heap(31533): Forcing collection of SoftReferences for 3850256-byte allocation
    10-14 13:44:12.480: DEBUG/dalvikvm(31533): GC_BEFORE_OOM freed 124K, 33% free 32200K/47431K, paused 37ms
    10-14 13:44:12.480: ERROR/dalvikvm-heap(31533): Out of memory on a 3850256-byte allocation.

I apologise for including so much logging, I hope it's relevant. The way I read it is that the system continuously readjusts heap size until it eventually reaches heap max. Then, we request an especially large allocation that fails. Clearly there is more than enough memory available (about 15 megs). Does this mean that heap is internally fragmented and there are no contiguous memory segments large enough to handle our allocation? If that's the case what should I do? If that's not it, then what?

Thanks in advance.

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

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

发布评论

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

评论(4

金橙橙 2024-12-17 11:00:34

奇怪的行为是因为位图是在本机堆上分配的,而不是在垃圾收集堆上分配的,但 android 只能跟踪垃圾收集堆上的对象。从 Android 2.2(或者可能是 2.3)开始,这种情况发生了变化,如果您进行堆转储,分配的位图也可见。

回到问题,您的问题很可能是您手动加载的位图没有正确释放。一个典型的问题是某些回调仍然设置或者视图仍然引用位图。
另一个常见问题是,如果您手动加载大位图(不是作为资源),当您不再需要它们时,您将需要对它们调用 recycle() ,这将从本机内存中释放位图,因此垃圾收集器将能够正常工作。 (GC只看到GC堆上的对象,并不知​​道要释放哪个对象来从本机堆中释放内存,实际上根本不关心它。)

我手边一直有这个小宝贝:

public static void stripImageView(ImageView view) {
    if ( view.getDrawable() instanceof BitmapDrawable ) {
        ((BitmapDrawable)view.getDrawable()).getBitmap().recycle();
    }
    view.getDrawable().setCallback(null);
    view.setImageDrawable(null);
    view.getResources().flushLayoutCache();
    view.destroyDrawingCache();
}

The weird behavior is because bitmaps are allocated on the native heap and not on the garbage collected, but android can only track objects on the garbage collected heap. From Android 2.2 (or 2.3 maybe) this has changed and allocated bitmaps are visible too if you make a heap dump.

Back to the question, your problem is most probably that the bitmaps you loaded manually are not freed appropriately. One typical problem is that some callback remains set or the view is still referring the bitmap.
The other common problem is that if you load big bitmaps manually (not as a resource), you will need to call recycle() on them when you don't need it anymore, which will free the bitmap from the native memory so the garbage collector will be able to its work as it should. (The GC only sees objects on the GC heap, and doesn't no which object to free to free memory from the native heap, and actually doesn't even care about it.)

I have this little baby at hand all the time:

public static void stripImageView(ImageView view) {
    if ( view.getDrawable() instanceof BitmapDrawable ) {
        ((BitmapDrawable)view.getDrawable()).getBitmap().recycle();
    }
    view.getDrawable().setCallback(null);
    view.setImageDrawable(null);
    view.getResources().flushLayoutCache();
    view.destroyDrawingCache();
}
耀眼的星火 2024-12-17 11:00:34

这些图像是从网络上获取的,每张图像大小从 300K 到 500K 不等
大小,并存储在 Drawables 的 arrayList 中。

您从网络加载的图像的 kb 文件大小并不直接相关。由于它们被转换为位图,因此您需要为常规 ARGB 图像计算每个图像的宽度 * 高度 * 4 字节。 (宽度和高度以 px 为单位)。

位图消耗本机堆,这通常不会显示在 hprof 中。 hprof 应该只显示对象的数量,即剩余的 BitmapDrawables 或 Bitmaps。

我在应用程序中使用此代码来输出应用程序和本机堆当前使用的内存:

public static void logHeap(Class clazz) {
    Double allocated = new Double(Debug.getNativeHeapAllocatedSize())/new Double((1048576));
    Double available = new Double(Debug.getNativeHeapSize())/1048576.0);
    Double free = new Double(Debug.getNativeHeapFreeSize())/1048576.0);
    DecimalFormat df = new DecimalFormat();
    df.setMaximumFractionDigits(2);
    df.setMinimumFractionDigits(2);

    Log.d(APP, "debug. =================================");
    Log.d(APP, "debug.heap native: allocated " + df.format(allocated) + "MB of " + df.format(available) + "MB (" + df.format(free) + "MB free) in [" + clazz.getName().replaceAll("com.myapp.android.","") + "]");
    Log.d(APP, "debug.memory: allocated: " + df.format(new Double(Runtime.getRuntime().totalMemory()/1048576)) + "MB of " + df.format(new Double(Runtime.getRuntime().maxMemory()/1048576))+ "MB (" + df.format(new Double(Runtime.getRuntime().freeMemory()/1048576)) +"MB free)");
    System.gc();
    System.gc();

    // don't need to add the following lines, it's just an app specific handling in my app        
    if (allocated>=(new Double(Runtime.getRuntime().maxMemory())/new Double((1048576))-MEMORY_BUFFER_LIMIT_FOR_RESTART)) {
        android.os.Process.killProcess(android.os.Process.myPid());
    }
}

在开发过程中开始或完成活动时我会调用它。

logHeap(this.getClass());

这里有一些信息链接 - 一般来说这里有很多关于这个主题的线程。

这里还有一张有用的幻灯片作者:Romain Guy(Android 框架工程师),关于软引用、弱引用、简单缓存、图像处理:http://docs.huihoo.com/google/io/2009/Th_0230_TurboChargeYourUI-HowtomakeyourAndroidUIfastandefficient.pdf

The images are fetched from the Web, each ranging from 300K to 500K in
size, and stored in an arrayList of Drawables.

The kb file size of the image you're loading from the web isn't directly relevant. Since they're converted into bitmaps you need to calculate width * height * 4 bytes per image for regular ARGB images. (width and height in px).

The bitmaps consume native heap, which usually doesn't show in a hprof. The hprof should only show you the number of objects, i.e. BitmapDrawables or Bitmaps that are left.

I use this code in my app to output the current used memory used by the app and native heap:

public static void logHeap(Class clazz) {
    Double allocated = new Double(Debug.getNativeHeapAllocatedSize())/new Double((1048576));
    Double available = new Double(Debug.getNativeHeapSize())/1048576.0);
    Double free = new Double(Debug.getNativeHeapFreeSize())/1048576.0);
    DecimalFormat df = new DecimalFormat();
    df.setMaximumFractionDigits(2);
    df.setMinimumFractionDigits(2);

    Log.d(APP, "debug. =================================");
    Log.d(APP, "debug.heap native: allocated " + df.format(allocated) + "MB of " + df.format(available) + "MB (" + df.format(free) + "MB free) in [" + clazz.getName().replaceAll("com.myapp.android.","") + "]");
    Log.d(APP, "debug.memory: allocated: " + df.format(new Double(Runtime.getRuntime().totalMemory()/1048576)) + "MB of " + df.format(new Double(Runtime.getRuntime().maxMemory()/1048576))+ "MB (" + df.format(new Double(Runtime.getRuntime().freeMemory()/1048576)) +"MB free)");
    System.gc();
    System.gc();

    // don't need to add the following lines, it's just an app specific handling in my app        
    if (allocated>=(new Double(Runtime.getRuntime().maxMemory())/new Double((1048576))-MEMORY_BUFFER_LIMIT_FOR_RESTART)) {
        android.os.Process.killProcess(android.os.Process.myPid());
    }
}

which I call when starting or finishing an activity during development.

logHeap(this.getClass());

Here are some informative links - generally there are lots of threads about this topic on here.

Here's also a useful slide by Romain Guy (Android Framework engineer) about soft references, weak references, simple caches, image handling: http://docs.huihoo.com/google/io/2009/Th_0230_TurboChargeYourUI-HowtomakeyourAndroidUIfastandefficient.pdf

牛↙奶布丁 2024-12-17 11:00:34

系统不会重新调整堆大小。您的应用程序有一个(半)预定义的堆空间。在某些自定义 ROM 中可以调整堆大小。但这与您的问题并不真正相关。

您看到的垃圾收集的顺序似乎表明,在 GC_BEFORE_OOM 发生时,您的应用程序耗尽了其堆空间。 Dalvik 记录器报告的统计数据可能是系统范围的。

The system doesn't readjust heap sizes. Your app has a (semi-) predefined heap space. Heap sizes can be adjusted in some custom ROMs. This isn't really germane to your problem though.

The order of the garbage collection you're seeing seems to suggest that at the time of the GC_BEFORE_OOM happens your app's running out of its heap space. The stats reported by the Dalvik logger could be system-wide.

雪化雨蝶 2024-12-17 11:00:34

是的,当我们使用大尺寸位图时,会发生“内存不足”错误。为此我有一个解决方案,通过图像解码我们可以轻松避免此错误。

BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
FileInputStream fis = new FileInputStream(files.getAbsolutePath());
BitmapFactory.decodeStream(fis, null, o);
fis.close();
final int REQUIRED_SIZE=70;
int width_tmp=o.outWidth, height_tmp=o.outHeight;
int scale=1;
while(true){
if(width_tmp/2<REQUIRED_SIZE || height_tmp/2<REQUIRED_SIZE)
break;
width_tmp/=2;
height_tmp/=2;
scale*=2;
}
BitmapFactory.Options op = new BitmapFactory.Options();
op.inSampleSize = scale;
fis = new FileInputStream(files.getAbsolutePath());
bitmap[i] = BitmapFactory.decodeStream(fis, null, op);
fis.close();

yes the error "out of Memory" occur when we are working with big size Bitmap. for that i have an solution, with Decoding of the image we can easily avoid this error.

BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
FileInputStream fis = new FileInputStream(files.getAbsolutePath());
BitmapFactory.decodeStream(fis, null, o);
fis.close();
final int REQUIRED_SIZE=70;
int width_tmp=o.outWidth, height_tmp=o.outHeight;
int scale=1;
while(true){
if(width_tmp/2<REQUIRED_SIZE || height_tmp/2<REQUIRED_SIZE)
break;
width_tmp/=2;
height_tmp/=2;
scale*=2;
}
BitmapFactory.Options op = new BitmapFactory.Options();
op.inSampleSize = scale;
fis = new FileInputStream(files.getAbsolutePath());
bitmap[i] = BitmapFactory.decodeStream(fis, null, op);
fis.close();
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文