2011-10-14 46 views
11

我有一个Windows控制台应用程序,应该在几天或几个月内不重新启动的情况下运行。该应用程序从MSMQ中检索“工作”并处理它。有30个线程同时处理工作块。来自队列的大对象堆和字符串对象

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

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

我通过探查器运行应用程序,并注意到大部分这种内存(可能是一个演出等)没有在大对象堆中使用,但结构是零散的。

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

据我所知,像GC.Collect(2或GC.Max ...)这样的东西不会帮助,因为他们gc大对象堆但不压缩它(这是这里的问题)。所以我认为我需要的是缓存这些字符串并以某种方式重新使用它们,但由于字符串是不可变的,我必须使用StringBuilders。

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

感谢, 雅尼斯

UPDATE:关于这些 “作品” 块目前如何检索

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

在工作节点侧目前我们做

WorkChunk块= _Queue.Receive();

所以在这个阶段我们没有什么可以缓存的。如果我们以某种方式改变了结构,那么我想我们可以取得一些进展。无论如何,我们必须解决这个问题,所以我们会尽一切努力避免抛出数月的工作。

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

+0

你是如何收到这些字符串的?一旦他们是字符串,你就卡住了。我来自一个流或字节[]你可能有一些选择。 –

+0

嗨亨克 - 看看更新以获取有关这些工作块的更多信息 – Yannis

+0

但这是一个实际的问题?具有> = 8GB RAM的64位PC上的1.5GB应该可以继续。 –

回答

4

您的问题似乎是由于大对象堆上的内存分配 - 大对象堆未压缩,因此可能是碎片来源。这里有一个很好的一篇文章,进入更多细节,包括您可以按照确认大对象堆破碎化一些调试步骤正在发生的事情:

Large Object Heap Uncovered

你似乎有 2个 三种解决方案:

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

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

这两个变化都涉及到改变你的应用程序来使用字节数组和短子串而不是单个大字符串来执行它的一些或全部处理 - 这对你来说有多困难取决于它是什么类型的处理你在做什么。

+0

谢谢贾斯汀。问题是这些字符串通过消息队列来自不同的系统。所以我现在不能说“获得一半的工作块”,除非我改变整体存储结构 - 我想这就是我需要的想法和建议 – Yannis

+0

@Yannis如果你想改变你的应用程序,那么它看起来就是这样 - 对于建议关于你如何做这件事,可能需要更多关于正在完成的处理的细节。你见过我最新的编辑吗?你应该认为你看到的这种行为可能是完全正确的(只要你没有得到OOM例外,这是一个32位还是64位的进程?) – Justin

+0

Justin - 这是一个64位的进程,结果是计算机(Windows 2008 Server)由于分页过多而变慢。这是有道理的。让我问一下:如果将字符串内容属性更改为char [] [],其中包含85k的char数据块的字符数组(这是将事情放在LOH上的限制) - 会有帮助吗? – Yannis

1

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

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

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

+0

这就是OP已经说过的。但是,如何重用字符串? –

+0

对原始文章添加了编辑信息 – Yannis

+0

Tony - 问题在于序列化这些内容并在另一端反序列化它们。无论我做什么,这个对象都会以这种或那种方式包含这些“内容” - 即使是在小块中。 – Yannis

2

当LOH出现碎片时,表示有分配的对象。如果您能延缓延迟,您可以稍等一会,直到所有当前正在运行的任务都完成并致电GC.Collect()。当没有引用大型对象时,它们都将被收集起来,有效地消除了LOH的碎片。当然,这只适用于(全部)所有大对象都未被引用的情况。

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

+0

Steven我认为你错了,因为碎片并不意味着对象在那里(在LOH中),但他们曾经在那里,并最终被解除分配从而在蕙兰留下了一块空白的大块。这意味着如果有一个120k的块(比如说),并且我们试图分配121k,那么这将在第一个可用的121k字节连续块中分配,从而使120k块空着。 不幸的是,GC.Collect()只会取消分配LOH对象(并且需要GC.Collect(GC.MaxGeneration))并且不会压缩LOH。 – Yannis

+1

我不认为史蒂文说GC.Collect会紧凑,我想他是说当你只有几件东西在旅途中叫它。这样它就会摆脱它们之间的巨大对象,并留下一个不错的(ish)干净的石板。 – Joey

+1

@Yannis:我的意思是:一个空的LOH不能被分割。乔伊很好地重述了它。 – Steven

0

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

+0

如果您可以将您的标题和内容切分为键/值对,并在所有键和值上执行.Intern,它会更好。然后你将不会有重复的数据,而是一个不同的数据结构,这可能需要更多的处理。 –