来自队列的大对象堆和字符串对象

我有一个Windows控制台应用程序应该运行几天和几个月没有重新启动。 该应用程序从MSMQ检索“工作”并处理它。 有30个线程同时处理工作块。

来自MSMQ的每个工作块大约为200kb,其中大部分工作块分配在单个String对象中。

我注意到,在处理了大约3-4千个这些工作块之后,应用程序的内存消耗非常高,消耗了1到1.5 GB的内存。

我通过分析器运行应用程序并注意到大部分内存(可能是一个演出)在大对象堆中未使用但结构是分段的。

我发现这些未使用的(垃圾收集)字节中有90%是先前分配的字符串。 我开始怀疑从MSMQ进来的字符串是分配,使用然后解除分配,因此是碎片的原因。

我明白像GC.Collect(2或GC.Max …)这样的东西不会有用,因为它们是gc大对象堆但不压缩它(这是问题)。 所以我认为我需要的是缓存这些字符串并以某种方式重用它们,但由于字符串是不可变的,我将不得不使用StringBuilders。

我的问题是:无论如何不改变底层结构(即使用MSMQ,因为这是我无法改变的),并且仍然避免每次初始化一个新的String以避免分裂LOH?

谢谢,雅尼斯

更新:关于如何检索这些“工作”块

目前,它们作为WorkChunk对象存储在MSMQ中。 这些对象中的每一个都包含一个名为Contents的String和另一个名为Headers的String。 这些是实际的文本数据。 如果需要,我可以将存储结构更改为其他内容,如果需要,可以将底层存储机制更改为MSMQ以外的其他内容。

目前我们在工作节点方面

WorkChunk chunk = _Queue.Receive();

所以在这个阶段我们几乎无法缓存。 如果我们以某种方式改变了结构,那么我想我们可以做一些进步。 在任何情况下,我们都必须解决这个问题,以便我们尽一切可能避免浪费数月的工作。

更新:我继续尝试下面的一些建议并注意到这个问题无法在我的本地计算机上运行(运行Windows 7 x64和64位应用程序)。 这使得事情变得更加困难 – 如果有人知道为什么那么它真的有助于在本地重新调整这个问题。

您的问题似乎是由于大对象堆上的内存分配 – 大对象堆没有压缩,因此可能是碎片的来源。 这里有一篇很好的文章,详细介绍了一些调试步骤,您可以遵循这些步骤来确认大对象堆的碎片是否正在发生:

大型物体堆未被覆盖

您似乎有两个三个解决方案:

  1. 改变您的应用程序以对块/较短字符串执行处理,其中每个块小于85,000字节 – 这避免了大对象的分配。
  2. 改变您的应用程序,预先分配一些大块内存,然后通过将新消息复制到分配的内存中来重新使用这些块。 使用字节数组时请参阅堆碎片 。
  3. 保持原样 – 只要您没有遇到内存不足exception且应用程序没有干扰系统上运行的其他应用程序,您应该保留原样。

理解虚拟内存和物理内存之间的区别非常重要 – 即使进程使用大量虚拟内存,如果分配的对象数量相对较少,那么该进程的物理内存使用率也很低(未使用的内存被分页到磁盘)意味着对系统上的其他进程几乎没有影响。 您还可能会发现“VM Hoarding”选项有助于 – 阅读“Large Object Heap Uncovered”文章以获取更多信息。

更改涉及更改应用程序以使用字节数组和短子串而不是单个大字符串执行其部分或全部处理 – 这对您来说有多困难将取决于您正在进行的处理类型。

当LOH上存在碎片时,意味着它上面有分配的对象。 如果您能够延迟延迟,您可以偶尔等待所有当前正在运行的任务完成并调用GC.Collect() 。 当没有被引用的大对象时,它们都将被收集,有效地消除了LOH的碎片。 当然,只有当(所有)所有大型对象都未被引用时,这才有效。

此外,迁移到64位操作系统也可能会有所帮助,因为由于碎片造成的内存不足会导致64位系统出现问题,因为虚拟空间几乎是无限的。

也许您可以创建一个字符串对象池,您可以在处理工作时使用它,然后在完成后返回。

一旦在LOH中创建了一个大对象,就无法将其删除(AFAIK),因此如果您无法避免创建这些对象,那么最好的计划是重用它们。

如果您可以在两端更改协议,那么将“内容”字符串减少为一组较小的字符串(每个<80k)应该阻止它们存储在LOH中。

如何使用String.Intern(…)来消除重复引用。 它有性能损失,但根据你的字符串,它可能会产生影响。