Анализируя клиентское приложение WCF (о котором я не писал и до сих пор не знаю слишком много), которое общается с кучей сервисов через SOAP и после запуска в течение нескольких дней выдает исключение OutOfMemoryException, я обнаружил это. net PooledBufferManager никогда не будет освобождать неиспользуемые буферы, даже когда приложению не хватает памяти, что приводит к OOMEs.
Это, конечно, в соответствии со спецификацией: http://msdn.microsoft.com/en-us/library/ms405814.aspx
Пул и его буферы [...] уничтожаются, когда пул буферов
утилизируется сборщиком мусора.
Пожалуйста, не стесняйтесь отвечать только на один из приведенных ниже вопросов, поскольку у меня есть несколько вопросов, некоторые из которых носят более общий характер, а некоторые специфичны для использования нашим приложением BufferManager.
Сначала пара общих вопросов о BufferManager (по умолчанию для пула):
1) В среде, где у нас есть GC, зачем нам нужен BufferManager, который будет удерживать неиспользуемую память, даже если это приводит к OOME? Я знаю, что есть BufferManager.Clear (), который вы можете использовать, чтобы вручную избавиться от всех буферов - если у вас есть доступ к BufferManager, то есть. Посмотрите далее, почему у меня нет доступа.
2) Несмотря на заявления MS о том, что «этот процесс намного быстрее, чем создание и уничтожение буфера каждый раз, когда вам нужно его использовать.», Если они не оставят это на усмотрение GC ( и его LOH например) а вместо ГХ оптимизировать?
3) При выполнении BufferManager.Take (33 *1024* 1024) я получу буфер размером 64 МБ, поскольку PooledBufferManager будет кешировать этот буфер для последующего повторного использования, что может, ну, в в моем случае это не так, и поэтому это пустая трата памяти - будь то, скажем, 34M, или 50M, или 64M. Так было ли разумно создать потенциально очень расточительный BufferManager, подобный этому, который используется (по умолчанию, я полагаю) HttpsChannelFactory? Мне не удается понять, как важна производительность для выделения памяти, особенно когда мы говорим о WCF и сетевых службах, с которыми приложение будет взаимодействовать каждые 10 секунд TOPS, обычно еще много секунд или даже минут.
Теперь некоторые более конкретные вопросы, связанные с использованием нашего приложения BufferManager. Приложение подключается к паре разных служб 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 НЕ использовать BufferManager вообще и попросить GC выполнить свою чертову работу, которая заключается в «управлении памятью»?
6) Если на вопросы 4) и 5) невозможно ответить, могу ли я получить доступ к BufferManager для всех экземпляров HttpsChannelFactory и вручную вызвать для них .Clear () - это далеко не наоптимальное решение, но оно уже поможет, в моем случае освободит не только упомянутые выше 64M, но и 64M + 32M + 16M + 8M + 4M + 2M только в одном экземпляре службы!Так что это само по себе сделало бы мое приложение более продолжительным без проблем с памятью (и нет, у нас нет проблемы утечки памяти, кроме BufferManager, хотя мы действительно потребляем много памяти и накапливаем много данных в течение курсамного дней, но это не проблема здесь)