【问题标题】:Defrag/decrease heap size on pre honeycomb碎片整理/减少预蜂窝上的堆大小
【发布时间】:2013-07-27 00:38:57
【问题描述】:

我开发了一个应用程序,它在启动时会解析一些相当大量的 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,并且永远不会关闭,所以我用完了外部内存空间来分配位图。

【问题讨论】:

  • 你能试试这个测试吗:在开始解析 JSON 之前,在 dalvik 堆中分配一个 1-2MB 的连续块(​​类似于 byte[])。当您完成 JSON 解析并开始位图之前,通过将其清空并调用 GC 来释放该块。

标签: android memory-management bitmap


【解决方案1】:

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

如果您的问题与本机堆无关 - 仅与您的 dalvik 堆有关。

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

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

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

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

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

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

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

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

【讨论】:

  • 喷出 System.gc() 调用似乎没有影响。在这种情况下,我使用的是 BitmapFactory.decodeStream,但在使用 BitmapFactory.decodeResource 的其他区域也存在故障。我在上面的问题中添加了更多日志输出
  • 如果我们想在 JSON 部分解决碎片问题,请提供更多信息,说明您如何解析 JSON(哪个库/函数)、它有多少 JSON、一个巨大的 JSON 对象或许多较小的,等等
  • 我正在使用 Jackson + Databinding 解析 JSON 以从 HTTP 请求中读取压缩的 InputStream。这是很多较小的物体。
  • 您使用的是流式模型还是树模型?您需要 JSON 中的所有数据还是只有一小部分有趣?你用的是 POJO 还是 JsonNode?​​span>
  • 我正在使用 POJO + 树模型。尝试通过分配一个空位图(保留外部)或分配一个空字节数组(保留 dalvik)来保留内存并没有帮助。我认为除了使用流解析器并放弃数据绑定之外,我对这些设备无能为力,因为 iOS 或 ICS+ 设备在解析这么多数据时完全没有问题(即使是在运行相同的手机硬件) GB 与 ICS)。不过我会接受你的回答。感谢大家的帮助
【解决方案2】:

首先,你在模拟器上运行吗?因为你的堆大小似乎有点太小了。根据我的计算,您总共使用了大约 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() 以立即释放其本机内存。

【讨论】:

  • 我在真正的 2.3 设备(例如 Droid 2)上运行,因此不能选择增加堆大小。所有位图加载都用 try/catch(OOM) 包装,在 catch 中我调用 System.gc() 并重试。但这似乎没有任何作用。不幸的是,虚拟机不会自动对堆进行碎片整理并适当地缩小它。这就是为什么本机堆永远不会获得足够的内存,因为 Dalvik 堆在启动时会吃掉所有资源。这种双堆解决方案令人难以置信的令人沮丧,我不明白为什么要以这种方式开始
  • 在 2.x 设备上,我总是(!)在任何位图解码之前执行 GC,而不是 try catch 并且仅在 OOM 上执行 GC。请试试这个。性能排在第二位。如果以后我们想要减少 GC 的数量,我们可以通过查询空闲堆大小来手动检查内存不足的情况
  • 在真实设备上工作,真的吗?帮助我们理解那些没有加起来的东西。日志中的数字没有加起来,我一直遇到的问题是实际堆使用量(对的第一个数字)而不是总堆大小(第二个)。您能否提供包括 CLAMP 行在内的完整崩溃日志?我们将深入了解这一点
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-01-17
  • 1970-01-01
  • 2017-08-21
  • 2016-04-25
  • 1970-01-01
  • 2011-01-17
相关资源
最近更新 更多