Как я могу предотвратить потерю памяти BufferManager / PooledBufferManager в моем клиентском приложении WCF? - PullRequest
13 голосов
/ 31 августа 2011

Анализируя клиентское приложение 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, хотя мы действительно потребляем много памяти и накапливаем много данных в течение курсамного дней, но это не проблема здесь)

Ответы [ 3 ]

8 голосов
/ 07 июня 2013

Мне кажется, у меня есть ответ на ваш вопрос № 5:

5) Или, может быть, даже лучше, мы могли бы просто рассказать всем HttpsChannelFactory случаи НЕ использовать BufferManager вообще и попросить GC сделать его проклятая работа - «управление памятью»?

Существует параметр привязки MaxBufferPoolSize, который контролирует максимальный размер буферов в BufferManager. Установка его в 0 отключит буферизацию, и вместо пула будет создан GCBufferManager - и он будет распределять буферы GC, как только будет обработано сообщение, как в вашем вопросе.

В этой статье более подробно обсуждается Управление буфером памяти WCF .

7 голосов
/ 31 августа 2011

4) Мы что-то здесь не так делаем? Я не эксперт WCF по любому означает, что мы можем сделать различные экземпляры HttpsChannelFactory все использовать тот же BufferManager?

5) Или, может быть, даже лучше, мы могли бы просто рассказать всем HttpsChannelFactory случаи НЕ использовать BufferManager вообще и попросить GC сделать его проклятая работа, которая «управляет памятью»?

Я полагаю, что одним из способов решения этих двух вопросов может быть изменение TransferMode с «buffered» на «streamed». Придется расследовать, так как режим «поток» имеет несколько ограничений, и я не смогу его использовать.

Обновление: Это на самом деле прекрасно работает! Мое потребление памяти в буферизованном режиме во время запуска приложения составляло 630M в часы пиковой нагрузки и уменьшалось до 470M при полной загрузке. После переключения в потоковый режим потребление памяти не показывает временный пик, а при полной загрузке потребление составляет всего 270M !

Кстати, для меня это было однострочное изменение в коде клиентского приложения. Мне просто нужно было добавить эту строку:

httpsTransportBindingElement.TransferMode = TransferMode.StreamedResponse;
3 голосов
/ 31 августа 2011

Как говорит Джон, было бы легче ответить только на один вопрос, а не написать эссе.Но вот что я думаю

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, и объем тестирования, который был выполнен на устаревшем проекте - следуйте бритве Occam.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...