Как я могу предотвратить потерю памяти BufferManager / PooledBufferManager в моем клиентском приложении WCF?
Анализируя клиентское приложение 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) я получу буфер размером 64 МБ, поскольку PooledBufferManager будет кешировать этот буфер для последующего повторного использования, что может - ну, в моем случае это не так, и поэтому чистая трата памяти - будь то, скажем, 34M, или 50M, или 64M. Так было ли разумно создать потенциально очень расточительный BufferManager, подобный этому, который используется (по умолчанию, я полагаю) HttpsChannelFactory? Мне не удается понять, как важна производительность для выделения памяти, особенно когда мы говорим о WCF и сетевых службах, с которыми приложение будет обращаться к TOPS каждые 10 секунд, обычно через много секунд или даже минут.
Теперь некоторые более конкретные вопросы, связанные с использованием нашего приложения 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, хотя мы действительно потребляем много памяти и накапливаем много данных в течение курса много дней, но это не проблема здесь)
3 ответа
4) Мы что-то здесь не так делаем? Я ни в коем случае не эксперт WCF, поэтому можем ли мы заставить различные экземпляры HttpsChannelFactory использовать один и тот же BufferManager?
5) Или, может быть, даже лучше, можем ли мы просто сказать всем экземплярам HttpsChannelFactory НЕ использовать BufferManager вообще и попросить GC выполнить свою чертову работу, которая заключается в "управлении памятью"?
Я думаю, что одним из способов решения этих двух вопросов может быть изменение TransferMode с "Buffered" на "Streamed". Придется расследовать, так как "потоковый" режим имеет несколько ограничений, и я не смогу его использовать.
Обновление: это на самом деле прекрасно работает! Мое потребление памяти в режиме буферизации во время запуска приложения составляло 630 МБ в часы пик, а при полной загрузке уменьшалось до 470 МБ. После переключения в потоковый режим потребление памяти не показывает временный пик, а при полной загрузке потребление составляет всего 270M!
Кстати, это было изменение одной строки в коде клиентского приложения для меня. Я просто должен был добавить эту строку:
httpsTransportBindingElement.TransferMode = TransferMode.StreamedResponse;
Я считаю, что у меня есть ответ на ваш вопрос № 5:
5) Или, может быть, даже лучше, можем ли мы просто сказать всем экземплярам HttpsChannelFactory НЕ использовать BufferManager вообще и попросить GC выполнить свою чертову работу, которая заключается в "управлении памятью"?
Существует параметр привязки MaxBufferPoolSize, который контролирует максимальный размер буферов в BufferManager. Установка его в 0 отключит буферизацию, и вместо пула будет создан GCBufferManager - и он будет распределять буферы GC, как только сообщение будет обработано, как в вашем вопросе.
В этой статье более подробно рассматривается управление буфером памяти WCF.
Как говорит Джон, было бы легче ответить только на один вопрос, а не писать эссе. Но вот что я думаю
1) В среде, где у нас есть GC, зачем нам BufferManager
Вы, кажется, неправильно понимаете концепцию GC и буферов. GC работает со ссылочными типизированными объектами и освобождает память, если обнаруживает, что объект является вершиной (точкой или узлом) графа и не имеет допустимых ребер (линий или соединений) с другими вершинами. Буферы - это просто временное хранилище для временного массива необработанных данных. Например, если вам нужно отправить сообщение уровня приложения WCF, и его текущий размер больше, чем размер сообщения транспортного уровня, WCF сделает это в нескольких транспортных сообщениях. Что касается размера получателя, WCF будет ждать, пока не придет полное сообщение прикладного уровня, и только тогда он передаст сообщение для обработки (если это не потоковая привязка). Временные транспортные сообщения буферизуются - хранятся где-то в памяти на стороне получателя. Поскольку создание новых буферов для любых новых сообщений в этом примере может стать очень обширным, .NET предоставляет вам класс управления буферами, который отвечает за объединение и совместное использование буферов.
2) Несмотря на заявления MS о том, что "этот процесс намного быстрее, чем создание и уничтожение буфера каждый раз, когда вам нужно его использовать", не следует ли оставить это на усмотрение GC (и его LOH, например) и оптимизировать GC вместо?
Нет, они не должны. Буферы и GC не имеют ничего общего (если только вы не хотите уничтожать буфер каждый раз, в контексте примера, что является недостатком дизайна). У них разные обязанности и они решают разные проблемы.
3) Приложение подключается к паре разных сервисов WCF. Для каждого из них мы поддерживаем пул соединений для http соединений
HTTP-привязка не предназначена для обработки больших полезных данных, таких как 64 МБ, рассмотрите возможность изменения привязки на более подходящую. Если вы используете сообщение этого sie, WCF не пропустит его, пока не будут получены полностью все 64 МБ. Таким образом, если у вас есть 10 одновременных подключений, ваш размер буфера будет 640 МБ.
По другим вопросам, пожалуйста, опубликуйте еще один вопрос о SO с некоторым кодом и вашей конфигурацией WCF. Будет легче найти, где проблема. Возможно, буферы не очищены, потому что они используются не по назначению, вы должны учитывать объем тестирования, который был выполнен на GC и WCF, и объем тестирования, который был выполнен на устаревшем проекте - следуйте бритве Оккама.