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

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

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

那么.NET 中的内存管理是否如此纯粹,以至于它甚至不允许完成如此简单的场景,还是我错过了一些东西?

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

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

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

有帮助吗?

解决方案

查看MSMQ的受管包装器的源代码后,您似乎已偶然发现使用API的真实问题。调用生成BeginPeek将创建一个属性集合,然后在传递给非托管API之前固定。只有在收到消息时,这些属性才会取消键,但要继续接收到您必须在此时呼叫生成的消息,导致内存碎片随着时间的推移。

如果这种碎片是一个真正的担忧,我可以唯一可以想出的是每小时左右,你应该取消所有呼叫的BeginPeek,强制垃圾收集,然后恢复正常的聆听操作。这应该允许垃圾收集器处理碎片。

其他提示

好吧,如果这是固定对象长期存在的问题,那么一个简单的解决方法是在 BeginPeek 持续时间较短,以防止他们进入下一代。每次超时后 EndPeek 您可以重新发出 BeginPeek. 。根据 Martin 引用的属性的创建和处置时间,您可能必须重新创建队列对象(显然不是队列本身,只是修改的包装器)。不过如果幸运的话,你不必走那么远。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top