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

本文关键字:对象 字符串 队列 | 更新日期: 2023-09-27 18:11:19

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

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

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

我通过分析器运行应用程序,并注意到大部分内存(可能是gig左右)在大对象堆中未被使用,但结构是碎片化的。

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

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

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

谢谢,Yannis

更新:关于当前如何检索这些"工作"块

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

当前在工作节点端我们执行

WorkChunk chunk = _Queue.Receive();

所以在这个阶段我们可以缓存的东西很少。如果我们以某种方式改变结构,那么我想我们可以取得一些进步。无论如何,我们必须解决这个问题,因此我们将采取一切必要措施,以避免浪费几个月的工作。

UPDATE:我继续尝试下面的一些建议,并注意到这个问题不能在我的本地机器上重现(运行Windows 7 x64和64位应用程序)。这使得事情变得更加困难——如果有人知道为什么,那么它将真正有助于在当地报道这个问题。

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

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

未覆盖的大对象堆

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

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

理解虚拟内存和物理内存之间的区别很重要——即使进程正在使用大量的虚拟内存,如果分配的对象数量相对较少,那么该进程的物理内存使用量可能较低(未使用的内存被分页到磁盘),这意味着对系统上其他进程的影响很小。您可能还会发现"虚拟机囤积"选项很有帮助-阅读"大型对象堆未覆盖"文章了解更多信息。

这两种改变都包括改变你的应用程序,使用字节数组和短子字符串来执行部分或全部的处理,而不是一个单一的大字符串——这对你来说有多困难取决于你正在做的是什么类型的处理。

如果LOH上存在碎片,则表示LOH上存在已分配的对象。如果您可以承受延迟,您可以偶尔等待,直到所有当前运行的任务完成并调用GC.Collect()。当没有被引用的大对象时,它们将被全部收集,有效地消除LOH的碎片化。当然,这只适用于(几乎)所有大对象都未被引用的情况。

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

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

一旦在LOH中创建了一个大对象,它就不能被删除,所以如果你无法避免创建这些对象,那么最好的计划是重用它们。

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

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