2011-08-31 76 views
12

分析一个WCF客户端应用程序(我没有写,但仍然不知道太多),通过SOAP与一堆服务对话,运行几天后将抛出一个OutOfMemoryException,我发现.net的即使应用程序内存不足,PooledBufferManager也永远不会释放未使用的缓冲区,从而导致OOME。如何防止WCF客户端应用程序中的BufferManager/PooledBufferManager浪费内存?

这当然符合规格为: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秒钟通过一次,通常是更多秒甚至更长的时间。

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

检查一个堆转储中一个最大的对象,一个64M字节的数组,它在初始化时只在应用程序中使用过一次,之后不再需要,因为服务的响应只在初始化时大,这btw。对于我已经使用的许多应用程序来说是典型的,尽管这可能会受到opimization(缓存到磁盘等)的影响。在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[]) 

在GC根用于由BufferManager管理的其他字节数组展望显示,其他服务(而不是'MyServiceX')有不同的BufferPool实例,所以每个人都在浪费自己的内存,他们甚至不共享这些浪费。

4)我们在这里做错了什么?我不是一个WCF专家,所以我们可以让各种HttpsChannelFactory实例都使用同一个BufferManager?

5)或者甚至更好,可能我们只是告诉所有HttpsChannelFactory情况下,不是在所有使用BufferManagers并要求GC尽自己的神该死的工作,这就是“管理存储”?

6)如果问题4)和5)不能回答,我能获得所有HttpsChannelFactory实例的BufferManager并对其手动调用.Clear() - 这是从最优解远,但这对我来说已经有所帮助了,它不仅能够在一个服务实例中释放前述的64M,而且还能够释放64M + 32M + 16M + 8M + 4M + 2M!因此,只有这样,才能让我的应用持续更长的时间,而不会遇到内存问题(并且不,我们没有内存泄漏问题,除了BufferManager,尽管我们消耗了大量内存并在课程中积累了大量数据很多天,但这不是问题)

+0

@ dlev's编辑:感谢您消除幽默。我很感激。 –

+1

请问每个问题有一个问题。 –

+3

@John Saunders - 并将样板复制到这些单独问题的每一个中?这似乎没有道理。尽管我把问题分开了,但它们都涉及到同样的问题。有人可能会说至少分裂了“一般”和“具体”的问题,但是,即使是一般性问题也涉及到我的具体情况,所以... –

回答

7

4)我们在这里做错了什么?我不是任何一个WCF专家的手段,所以我们可以使各种HttpsChannelFactory实例全部使用相同的BufferManager? ?

5),或者甚至更好,可能我们只是告诉所有HttpsChannelFactory 情况下,不是在所有使用BufferManagers并要求GC做了 神该死的工作,这就是“管理存储”?

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

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

顺便说一下,这是客户端应用程序代码对我的一个单行更改。我只需要添加下面这一行:

httpsTransportBindingElement.TransferMode = TransferMode.StreamedResponse; 
+0

我知道这个问题有点老,但我想我会问一个简单的问题。我也不是WCF专家。我有同样的问题,并希望使用流媒体。这在客户端真的有可能吗?我有一个服务引用,它自动为我构建客户端类代码,但我在哪里将此绑定元素附加到客户端对象?我还没有找到一个可以让我申请的财产。 – jlafay

+1

@jlafay如果你没有以编程方式创建BindingElements等,你应该看看你的配置文件(对于服务器,它将是Web.config,不知道如果对于客户端它是app.config或WCF的东西是存储)。看看例如http://blogs.msdn.com/b/drnick/archive/2006/03/31/565558.aspx负责转换为流模式的元素。 –

3

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

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

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

2)尽管MS”要求的,‘这个过程比 快得多创建和销毁缓冲区每次你需要使用一次。’, 不应该离开,截至到GC(和它的LOH为例)和 优化GC代替?

不,他们不应该。缓冲区和GC没有任何共同之处(除非你想每次破坏缓冲区,在样本的情况下,这是一个设计缺陷)。他们有不同的责任并解决不同的问题。

3)该应用程序连接到几个不同的WCF服务。对于每个 他们,我们维持一个连接池的HTTP连接

HTTP绑定不是设计来处理大量有效载荷像64MB,考虑更改绑定到更合适的一个。如果你使用那个消息,WCF将不会通过它,除非整个64Mb被完全接收。因此,如果你有10个并发连接,你的缓冲区大小将是640Mb。

对于其他问题,请在SO上发布另一个问题,其中包含一些代码和WCF配置。这将更容易找到问题所在。也许缓冲区没有被清除,因为它们使用不当,你应该考虑在GC和WCF上完成的测试数量以及在传统项目上执行的测试数量 - 遵循Occam的剃须刀。

+0

我明白GC和缓冲区之间的区别。我不明白在哪里使用缓冲区,而不是把它留给GC。 GC应该允许快速分配和自动释放内存,这是它的工作。它应该足够快以便为每个请求分配一个字节数组。当然,如果你编写的代码,比如说迭代一个流,以块的形式读取它,你将手动分配一个缓冲区,并且只分配一次,然后填充并重新填充直到完成。不需要缓冲区管理器。你有缓冲区管理器的用例吗?我没有看到WCF请求/响应是一个。 –

+1

关于“将绑定更改为更合适的绑定”,请参阅我自己的答案。将传输模式更改为“流式传输”似乎有诀窍。 –

+1

我创建了一个新问题http://stackoverflow.com/questions/7265299,只要我能接受我自己的答案(它只回答该问题的特定部分)就会关闭这个问题,或者出现一些更好的答案,无论何时出现第一。 –

8

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

5),或者甚至更好,我们能告诉所有HttpsChannelFactory 情况下没有在所有使用BufferManagers并要求GC做 其该死的工作,这是'管理记忆'?

有一个MaxBufferPoolSize绑定参数,它控制BufferManager中缓冲区的最大大小。将它设置为0将禁用缓冲,并且GCBufferManager将被创建而不是共用一个 - 并且一旦处理完消息,GC将分配缓冲区,就像你的问题一样。

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

+0

有趣。感谢您的链接。在我的情况下,迁移到'TransferMode.Streamed'解决了这个问题,我完全满意,因为它不仅解决了这些缓冲区的清理问题,而且完全摆脱了它们的创建。 –

相关问题