5

我有一个从大量 MSMQ 队列(目前大约 10000 个)读取的应用程序。我使用queue.BeginPeekUInt32.MaxValue 超时从队列接收消息。当消息出现在队列中时,我对其进行处理并queue.BeginPeek再次调用。所以我听所有的队列,但是消息处理是在线程池上完成的。

我注意到内存使用量缓慢增长(两周的工作导致从 200 MB 增长到 800 MB)。在调查了转储文件后,我看到了带有许多空闲对象的典型堆碎片图片(其中一些有大约几兆字节的大小)。并且孔之间有固定的物体。

在处理创建固定对象的非托管代码调用时,这似乎很常见。但是我在互联网上没有找到任何解决方案。

.NET 中的内存管理是否如此纯粹,以至于它甚至不允许完成如此简单的场景,或者我错过了什么?

编辑:我对示例应用程序进行了一些调查。在为新对象分配内存时,固定对象之间的空洞(空闲内存区域,所谓的空闲对象)被 GC 重用。但是在我的生产应用程序中,固定对象是长期存在的,它们最终出现在第二代,它们之间有孔(因为 GC 只是移动了分隔代的边界)。由于我几乎没有正常的长寿命对象,因此我在转储文件的第二代中看到了这个漏洞。

所以我的应用程序的内存消耗可以增长到 10000*(孔的平均大小)。(10000 是将来还可以增加的队列数)。我目前不知道如何解决这个问题。唯一的方法是不时重新启动应用程序。

再一次我只能问,为什么 .NET 没有用于固定对象的单独堆?(也许这是新手问题)。目前我看到调用使用非托管代码的异步操作可能会导致内存问题。

4

2 回答 2

4

查看 MSMQ 托管包装器的源代码后,您似乎偶然发现了使用 API 的真正问题。调用BeginPeek将创建一组属性,然后在传递给非托管 API 之前将其固定。仅当收到消息时,这些属性才会取消固定,但要继续接收消息,您必须BeginPeek在此时调用,从而导致内存碎片随着时间的推移。

如果这种碎片是一个真正的问题,我能想出的唯一技巧是每隔一小时左右,你应该取消所有对 的调用BeginPeek,强制进行垃圾收集,然后恢复正常的监听操作。这应该允许垃圾收集器处理碎片。

于 2012-10-05T12:01:25.397 回答
1

好吧,如果固定对象的寿命很长是一个问题,那么一个简单的解决方法是在BeginPeek更短的时间内使用超时来阻止它们进入下一代。每次超时后,EndPeek您可以重新发出BeginPeek. 根据 Martin 所指的属性的创建和处置时间,您可能必须重新创建队列对象(显然不是队列本身,只是经过组合的包装器)。尽管运气好的话,您不必走那么远。

于 2012-10-05T12:25:41.243 回答