0

我开发了一个应用程序,它在启动时会解析一些相当大量的 JSON,这会导致堆增长接近其极限。稍后,该应用程序正在加载几个位图。在 Honeycomb 及以上设备上,似乎使用相同的内存区域进行位图分配以及 Java 对象分配,这不会导致任何问题,因为那时 JSON 解析所需的内存已被释放。但是在预蜂窝设备上,它会导致 OutOfMemoryErrors

由于堆大小永远不会下降,dalvik 似乎无法增加为外部分配(例如位图)保留的内存。

这是崩溃的一个示例日志输出

GC_EXTERNAL_ALLOC freed 601K, 81% free 5504K/27591K, external 4809K/5561K, paused 58ms
586224-byte external allocation too large for this process.

如您所见,堆上有大量可用内存,但用于外部分配​​的内存不多。

有没有办法可以强制 dalvik 进行碎片整理和缩小堆?或者我可以强制位图分配发生在堆上,而不是为外部分配保留的内存?或者还有其他方法可以解决我错过的这个问题吗?

** 更新 **

这是更多日志输出(特定设备不记录 dalvik-heap 消息):

在解析 JSON 时启动时,dalvik 堆会增长:

GC_CONCURRENT freed 800K, 19% free 12717K/15687K, external 2637K/2773K, paused 2ms+5ms
GC_CONCURRENT freed 871K, 19% free 13857K/16903K, external 2637K/2773K, paused 2ms+5ms
GC_CONCURRENT freed 1106K, 19% free 14766K/18055K, external 2637K/2773K, paused 3ms+5ms
GC_CONCURRENT freed 818K, 16% free 15946K/18951K, external 2637K/2773K, paused 3ms+6ms
GC_CONCURRENT freed 825K, 15% free 17151K/20167K, external 2637K/2773K, paused 2ms+6ms
GC_CONCURRENT freed 830K, 15% free 18356K/21383K, external 2637K/2773K, paused 2ms+5ms
GC_CONCURRENT freed 814K, 14% free 19519K/22535K, external 2637K/2773K, paused 2ms+6ms
GC_CONCURRENT freed 823K, 13% free 20720K/23751K, external 2637K/2773K, paused 2ms+5ms
GC_CONCURRENT freed 814K, 13% free 21873K/24903K, external 2637K/2773K, paused 3ms+6ms
GC_CONCURRENT freed 813K, 12% free 23016K/26055K, external 2637K/2773K, paused 2ms+5ms
GC_CONCURRENT freed 1771K, 15% free 23205K/27207K, external 2637K/2773K, paused 2ms+5ms

完成后,大部分堆再次成功释放:

GC_EXPLICIT freed 19207K, 83% free 4735K/27207K, external 2736K/2773K, paused 140ms

此时我有 20 MB 的可用 Dalvik 堆空间,稍后开始分配位图:

GC_EXTERNAL_ALLOC freed 254K, 83% free 4814K/27207K, external 2771K/2773K, paused 47ms
GC_EXTERNAL_ALLOC freed 721K, 83% free 4880K/27207K, external 3881K/4131K, paused 50ms
GC_EXTERNAL_ALLOC freed 235K, 83% free 4870K/27207K, external 5398K/5561K, paused 62ms

总设备限制似乎是 32 MB,dalvik 堆占用了其中的 27 MB,并且永远不会下降,所以我用完了外部内存空间来分配位图。

4

2 回答 2

0

首先,你在模拟器上运行吗?因为你的堆大小似乎有点太小了。根据我的计算,您总共使用了大约 11MB(dalvik+native)后崩溃了,而且这很少。只是为了验证一下,您的崩溃后是否有类似的线路?

08-22 18:16:24.209: I/dalvikvm-heap(471): Clamp target GC heap from 24.610MB to 24.000MB

设备之间的最大堆大小(dalvik+native)不同。最小为 16MB,屏幕分辨率越大,它就越大。480x800 的设备通常有 32MB 以上。您可以在模拟器的 AVD 设置中控制堆大小,确保它足够大以反映真实设备。

关于您的问题,您不应该自己支付堆费用。VM 会自动执行此操作。确实,整个原生堆混乱使 GC 更难估计问题,因此您会得到一些 OutOfMemoryErrors,您可以通过在大分配之前手动运行 GC 来避免这些错误。

更具体地说,您的大分配发生在解析位图时(在像这样的调用期间BitmapFactory.decodeXXX())。根据我的经验,它实际上有助于System.gc()在他们之前运行。您只需在预 Honeycomb 设备上执行此操作。

另一个技巧是尽快释放本机分配。位图的像素位于本机堆中。它们仅在位图终结器上被释放,如您所知,它需要很长时间才能运行。只有在多次运行系统 GC 后才会释放此内存。这就是为什么建议自己手动释放它的原因。完成任何位图后,立即调用Bitmap.recycle()以释放其本机内存。

于 2013-07-29T21:34:24.920 回答
0

我想尝试从一个完全不同的角度来解决这个问题。Android 上的内存问题 = 该死的原生堆和位图 90% 的时间.. 但这并没有在这里加起来。据我所知,本机堆几乎没有限制,可以随心所欲地增长。我记得的唯一限制是 dalvik GC 中的手动测试,以确保您使用的dalvik 堆加上您使用的本机堆不会超过您的应用程序堆限制。

如果您的麻烦与本机堆无关怎么办 - 仅与您的 dalvik 堆有关。

我假设您失败的 500kb 分配位于本机堆中(对于位图像素)。这个分配有可能实际上在你的 dalvik 堆中。由于您的堆是如此碎片化,系统无法在您的 dalvik 堆中找到连续的 500kb 块,因此您崩溃了。

您使用的是哪个位图解码功能?有些方法直接从资源解码到本机堆,有些方法(例如使用字节数组的方法)是从 dalvik 堆上的缓冲区解码,这将解释那里的大分配。

要解决此问题,请更改您的位图解码功能并确保它根本不在 dalvik 堆上分配,或者更好的是,让我们从源头解决您的整个碎片问题。

以下是解决一般碎片问题的一些想法(在开始的 JSON 处理部分应用这些想法):

  1. System.gc()在战略位置手动运行。热门地点是在销毁大型对象期间,例如活动 onDestroy。或者,每当你完成一个大的 JSON 块时,就在其中抛出一个手动 GC。

  2. 协助 System GC 更快释放资源。这将首先防止您的堆变得碎片化。网上有很多关于如何做到这一点的材料。一些从我的头顶:

    • 如果您有频繁的小分配,请尝试采用内存重用方案,例如创建一个对象池,并重用池中的对象而不是分配新的对象。这种优化经常出现在 android 适配器中(比如 listviews 中的视图重用)
    • 当您不需要变量时手动将它们设置为 null,以便显式断开它们与参考图的连接并标记它们以便于 GC(显式清除数据集合也是如此)
    • 当你有大量操作时避免不可变类,比如字符串的连接,使用StringBuilder而不是常规String的 s
    • 完全避免终结器
    • 避免循环引用,或者至少将其中一个更改为弱引用
    • 一般在需要的地方使用弱引用

你可以通过确保你的堆在你只使用它的一小部分时不会增长到巨大的大小来衡量你的成功程度

于 2013-07-30T06:50:34.183 回答