如何防止WCF客户端应用程序中的BufferManager / PooledBufferManager浪费内存?

时间:2011-08-31 03:59:43

标签: c# .net wcf buffer-manager

分析一个WCF客户端应用程序(我没有写,但仍然不太了解)通过SOAP与一堆服务进行通信,运行几天后会抛出OutOfMemoryException,我发现了这一点。 net的PooledBufferManager永远不会释放未使用的缓冲区,即使应用程序内存不足,也会导致OOME。

这当然符合规范:http://msdn.microsoft.com/en-us/library/ms405814.aspx

  

当缓冲池为时,池及其缓冲区被销毁   通过垃圾收集回收。

请随时回答下面的一个问题,因为我有一堆问题,一些更一般的问题,以及我们的应用程序使用BufferManager的一些问题。

首先关于(默认池)BufferManager的几个一般性问题:

1)在我们拥有GC的环境中,为什么我们需要一个可以保留未使用内存的BufferManager,即使它导致OOME?我知道,有BufferManager.Clear(),你可以用它来手动摆脱所有的缓冲区 - 如果你有权访问BufferManager,那就是。请进一步了解我似乎无法访问的原因。

2)尽管MS声称“​​这个过程比每次需要使用一个缓冲区创建和销毁缓冲区要快得多。”,他们不应该把它留给GC (以及它的LOH)并优化GC?

3)当做一个BufferManager.Take(33 * 1024 * 1024)时,我会得到一个64M的缓冲区,因为PooledBufferManager将缓存该缓冲区以供以后重用,这可能 - 好吧,在我的情况下,它不是,因此它纯粹是浪费内存 - 例如,34M,或50M,或64M,是需要的。因此创建一个可能非常浪费的BufferManager是明智的,HttpsChannelFactory使用(默认情况下,我假设)?我没有看到内存分配的性能如何重要,特别是当我们讨论WCF和网络服务时,应用程序将每隔10秒TOPS,通常多几秒甚至几分钟。

现在有一些更具体的问题与我们的应用程序使用BufferManagers有关。该应用程序连接到几个不同的WCF服务。对于每个人,我们为http连接维护一个连接池,因为连接可能同时发生。

检查一个堆转储中的单个最大对象,一个64M字节数组,该数组在初始化时仅在我们的应用程序中使用过一次,之后不再需要,因为来自服务的响应仅在初始化时很大,顺便说一句。对于我使用过的许多应用程序来说都是典型的,即使这可能会受到优化(缓存到磁盘等)。 WinDbg中的GC根分析产生以下结果(我将我们的专有类的名称清理为'MyServiceX'等):

0:000:x86> !gcroot -nostacks 193e1000
DOMAIN(00B8CCD0):HANDLE(Pinned):4d1330:Root:0e5b9c50(System.Object[])->
035064f0(MyServiceManager)->
0382191c(MyHttpConnectionPool`1[[MyServiceX, MyLib]])->
03821988(System.Collections.Generic.Queue`1[[MyServiceX, MyLib]])->
038219a8(System.Object[])->
039c05b4(System.Runtime.Remoting.Proxies.__TransparentProxy)->
039c0578(System.ServiceModel.Channels.ServiceChannelProxy)->
039c0494(System.ServiceModel.Channels.ServiceChannel)->
039bee30(System.ServiceModel.Channels.ServiceChannelFactory+ServiceChannelFactoryOverRequest)->
039beea4(System.ServiceModel.Channels.HttpsChannelFactory)->
039bf2c0(System.ServiceModel.Channels.BufferManager+PooledBufferManager)->
039c02f4(System.Object[])->
039bff24(System.ServiceModel.Channels.BufferManager+PooledBufferManager+BufferPool)->
039bff44(System.ServiceModel.SynchronizedPool`1[[System.Byte[], mscorlib]])->
039bffa0(System.ServiceModel.SynchronizedPool`1+GlobalPool[[System.Byte[], mscorlib]])->
039bffb0(System.Collections.Generic.Stack`1[[System.Byte[], mscorlib]])->
12bda2bc(System.Byte[][])->
193e1000(System.Byte[])

查看由BufferManager管理的其他字节数组的gc根显示其他服务(不是“MyServiceX”)具有不同的BufferPool实例,因此每个实例都在浪费自己的内存,它们甚至不会共享浪费。

4)我们在这里做错了吗?我不是任何WCF专家,所以我们可以使各种HttpsChannelFactory实例都使用相同的BufferManager吗?

5)或者甚至更好,我们可以告诉所有HttpsChannelFactory实例根本不要使用BufferManagers并要求GC完成它的神秘工作,即“管理内存”吗? / p>

6)如果问题4)和5)无法回答,我是否可以访问所有HttpsChannelFactory实例的BufferManager并手动调用它们上的.Clear() - 这远非关于最佳解决方案,但它已经有所帮助,在我的情况下,它不仅可以在一个服务实例中释放前提的64M,而且64M + 32M + 16M + 8M + 4M + 2M!因此,单独这样可以使我的应用程序持续更长时间而不会遇到内存问题(不,除了BufferManager之外,我们没有内存泄漏问题,尽管我们确实消耗了大量内存并在课程中累积了大量数据很多天,但这不是问题)

3 个答案:

答案 0 :(得分:8)

我相信我已回答你的问题#5:

  

5)或者甚至更好,我们可以告诉所有HttpsChannelFactory   实例根本不使用BufferManagers并要求GC执行   这是该死的工作,是“管理记忆”?

有一个MaxBufferPoolSize绑定参数,它控制BufferManager中缓冲区的最大大小。将其设置为0将禁用缓冲,并且将创建GCBufferManager而不是池化 - 并且一旦消息处理就会GC分配缓冲区,如您的问题所示。

本文将更详细地讨论WCF memory buffer management

答案 1 :(得分:7)

  

4)我们在这里做错了吗?我不是任何WCF专家   意味着,我们可以制作各种HttpsChannelFactory实例   使用相同的BufferManager?

     

5)或者甚至更好,我们可以告诉所有HttpsChannelFactory   实例根本不使用BufferManagers并要求GC执行它   该死的工作,是“管理记忆”?

我想解决这两个问题的一种方法是将TransferMode从'buffered'更改为'streamamed'。将不得不调查,因为'流式'模式有一些限制,我可能无法使用它。

更新:它确实很棒!在启动应用程序时,我在缓冲模式下的内存消耗在高峰时间 630M ,在满载时减少到 470M 。切换到流模式后,内存消耗不会显示临时峰值,当满载时,消耗仅 270M

顺便说一句,这是我在客户端应用程序代码中的一行更改。我只需要添加这一行:

httpsTransportBindingElement.TransferMode = TransferMode.StreamedResponse;

答案 2 :(得分:3)

正如约翰所说,只回答一个问题而不是写一篇文章会更容易。但这就是我的想法

  

1)在我们拥有GC的环境中,为什么我们需要一个   BufferManager

您似乎误解了GC和缓冲区的概念。 GC与引用类型对象一起使用,如果它检测到对象是图形的顶点(点或节点)并且它没有任何有效的边(线或连接)到其他顶点,则释放内存。缓冲区只是临时存储数据的临时存储。例如,如果您需要发送WCF应用程序级别消息并且其当前大小大于传输级别消息大小,则WCF将在一些传输消息中执行此操作。在接收器大小上,WCF将等待,直到完整的应用程序级别消息到达,然后它才会传递消息进行处理(除非它是流式绑定)。临时传输消息缓冲 - 存储在接收器端的内存中。由于在此示例中为任何新消息创建新缓冲区可能会非常广泛,因此.NET为您提供了一个缓冲管理类,负责汇集和共享缓冲区。

  

2)尽管MS声称“​​这个过程比...快得多”   每次需要使用缓冲区时创建和销毁缓冲区。“,   他们不应该把它留给GC(例如它的LOH)和   改为优化GC?

不,他们不应该。缓冲区和GC没有任何共同之处(除非您希望每次都破坏缓冲区,在样本的上下文中,这是一个设计缺陷)。他们有不同的责任,解决不同的问题。

  

3)该应用程序连接到几个不同的WCF服务。对于每一个   他们为http连接维护一个连接池

HTTP绑定不是设计用于处理像64Mb这样的大型有效负载,考虑将绑定更改为更合适的绑定。如果您使用该sie的消息,除非完全接收到整个64Mb,否则WCF将不会传递它。因此,如果您有10个并发连接,则缓冲区大小将为640Mb。

对于您的其他问题,请在SO上发布另一个问题,其中包含一些代码和您的WCF配置。找到问题的位置会更容易。也许缓冲区没有被清除,因为它们被不恰当地使用,您应该考虑在GC和WCF上进行的测试量以及在遗留项目上执行的测试量 - 遵循Occam的剃刀。