Анализ клиентского приложения 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-соединений, поскольку соединения могут возникать одновременно.
Проверка единственного самого большого объекта в одном дампе кучи - массив размером 64 Мбайт, который использовался только один раз в нашем приложении во время инициализации и не нужен впоследствии, так как ответ от службы такой большой, только во время инициализации, что кстати. является типичным для многих приложений, которые я использовал, хотя это может быть подвергнуто опиомизации (кэширование на диск и т.д.). Анализ GC-корня в WinDbg дает следующее (я дезинфицировал имена наших собственных классов в "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) не могут быть отвечены, я могу получить доступ к BufferManager из всех экземпляров HttpsChannelFactory и вручную вызвать .Clear() на них - это далеко не на оптимальном решении, но это уже помогло бы в моем случае освободить не только вышеупомянутый 64M, но и 64M + 32M + 16M + 8M + 4M + 2M только в одном экземпляре службы! Таким образом, это само по себе заставит мое приложение работать дольше, не сталкиваясь с проблемами памяти (и нет, у нас нет проблемы с утечкой памяти, кроме BufferManager, хотя мы потребляем много памяти и накапливаем много данных по курсу много дней, но это не проблема здесь)