如何防止 WCF 客户端应用程序中的 BufferManager/PooledBufferManager 浪费内存? [英] How can I prevent BufferManager / PooledBufferManager in my WCF client app from wasting memory?

查看:24
本文介绍了如何防止 WCF 客户端应用程序中的 BufferManager/PooledBufferManager 浪费内存?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Analyzing a WCF client application (that I did not write and still do not know too much about) that talks to a bunch of services via SOAP and after running for a couple of days will throw an OutOfMemoryException, I found out that .net's PooledBufferManager will never ever release unused buffers, even when the app runs out of memory, leading to OOMEs.

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

当缓冲池被破坏时,缓冲池和它的缓冲区被[...]通过垃圾收集回收.

The pool and its buffers are [...] destroyed when the buffer pool is reclaimed by garbage collection.

请随意回答以下问题中的一个,因为我有很多问题,有些是更一般的问题,有些是针对我们的应用程序对 BufferManager 的使用.

Please feel free to answer to only a single of the questions below, as I have a bunch of questions, some of a more general nature, and some specific to our app's use of the BufferManager.

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

First a couple of general questions about the (default Pooled)BufferManager:

1) 在我们有 GC 的环境中,为什么我们需要一个 BufferManager 来保留未使用的内存,即使这会导致 OOME?我知道,有 BufferManager.Clear(),您可以使用它手动清除所有缓冲区 - 如果您有权访问 BufferManager,那就是.进一步查看为什么我似乎没有访问权限.

1) In a environment where we have GC, why would we need a BufferManager that will hold on to unused memory, even when that leads to OOME? I know, there is BufferManager.Clear(), which you can use to manually get rid off all buffers - if you have access to the BufferManager, that is. See further down for why I don't seem to have access.

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

2) Despite of MS' claim that "This process is much faster than creating and destroying a buffer every time you need to use one.", shouldn't they leave that up to the GC (and its LOH for example) and optimize the GC instead?

3) 执行 BufferManager.Take(33 * 1024 * 1024) 时,我将获得 64M 的缓冲区,因为 PooledBufferManager 将缓存该缓冲区以供以后重用,这可能 - 好吧,在我的情况下它不是,因此它纯粹是浪费内存 - 比如说,需要 34M、50M 或 64M.那么创建这样一个潜在的非常浪费的 BufferManager 是否明智,它被 HttpsChannelFactory 使用(默认情况下,我假设)?我不明白内存分配的性能应该如何重要,尤其是当我们谈论 WCF 和应用程序每 10 秒 TOPS 通信一次的网络服务时,通常需要更多秒甚至几分钟.

3) When doing a BufferManager.Take(33 * 1024 * 1024), I will get a buffer of 64M, as the PooledBufferManager will cache that buffer for later reuse, which might - well, in my case it isn't and therefore it's pure waste of memory - be that, say, 34M, or 50M, or 64M, are needed. So was it wise to create a potentially very wasteful BufferManager like this, that is used (by default, I assume) by HttpsChannelFactory? I'm failing to see how the performance for memory allocation should matter, especially when we are talking about WCF and network services that the application will talk to every 10 seconds TOPS, normally many more seconds or even minutes.

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

Now some more specific questions related to our application's use of BufferManagers. The app connects to a couple of different WCF services. For each of them we maintain a connection pool for the http connections, as connections may occur concurrently.

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

Inspecting the single biggest object in one heap dump, a 64M byte array that had only been used once in our app at initialization time and is not needed afterwards, as the response from the service is that big only at initialization time, which btw. is typical for many applications I have used, even though that could be subject to opimization (caching to disk etc.). A GC root analysis in WinDbg yields the following (I sanitized the names of our proprietary classes to 'MyServiceX', etc.):

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 实例,因此每个服务都在浪费自己的内存,他们甚至没有共享浪费.

Looking at gc roots for other byte arrays managed by a BufferManager reveals that other services (not 'MyServiceX') have different BufferPool instances, so each one is wasting their own memory, they are not even sharing the waste.

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

4) Are we doing something wrong here? I'm not a WCF expert by any means, so could we make the various HttpsChannelFactory instances all use the same BufferManager?

5) 或者甚至更好,我们可以告诉所有 HttpsChannelFactory 实例根本不要使用 BufferManagers 并要求 GC 完成它该死的工作,即管理内存"?

5) Or maybe even better, could we just tell all HttpsChannelFactory instances NOT to use BufferManagers at all and ask the GC to do its god-damn job, which is 'managing memory'?

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

6) If questions 4) and 5) can't be answered, could I get access to the BufferManager of all HttpsChannelFactory instances and manually call .Clear() on them - this is far from on optimal solution, but it would already help, in my case, it would free not only the aformentioned 64M, but 64M + 32M + 16M + 8M + 4M + 2M just in one service instance! So that alone would make my app last much longer without running into memory problems (and no, we don't have a memory leak issue, other than BufferManager, although we do consume a lot of memory and accumulate a lot of data over the course of many days, but that's not the issue here)

推荐答案

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

4) Are we doing something wrong here? I'm not a WCF expert by any means, so could we make the various HttpsChannelFactory instances all use the same BufferManager?

5) 或者甚至更好,我们可以告诉所有 HttpsChannelFactory实例根本不使用 BufferManagers 并要求 GC 做它的该死的工作,这是管理内存"?

5) Or maybe even better, could we just tell all HttpsChannelFactory instances NOT to use BufferManagers at all and ask the GC to do its god-damn job, which is 'managing memory'?

我想解决这两个问题的一种方法可能是将 TransferMode 从缓冲"更改为流式".必须进行调查,因为流式"模式有一些限制,我可能无法使用它.

I guess one way of addressing those 2 questions could be changing the TransferMode from 'buffered' to 'streamed'. Will have to investigate, as 'streamed' mode has a couple of limitations and I might not be able to use it.

更新:实际上效果很好!应用程序启动期间缓冲模式的内存消耗在高峰时间为 630M,在完全加载时减少到 470M.切换到流式模式后,内存消耗不会出现暂时的峰值,满载时,消耗仅为270M

Update: It actually works great! My memory consumption in buffered mode during startup of the app was 630M at peak times, and reduced to 470M when fully loaded. After switching to streamed mode, memory consumption does not show a temporary peak and when fully loaded, consumption is at only 270M!

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

Btw., this was a one-line change in the client app code for me. I just had to add this line:

httpsTransportBindingElement.TransferMode = TransferMode.StreamedResponse;

这篇关于如何防止 WCF 客户端应用程序中的 BufferManager/PooledBufferManager 浪费内存?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆