Освободить память от словаря С#, содержащегося в статическом объекте

У меня были некоторые проблемы с веб-службой WCF (некоторые дампы, утечки памяти и т.д.), и я запускаю инструмент profillng (профили памяти ANTS).

Просто чтобы узнать, что даже с обработкой (я запускаю определенный тест, а затем остановился), Generation 2 составляет 25% от объема памяти для веб-службы. Я отследил эту память, чтобы найти, что у меня есть словарь, полный (нулевые, нулевые) элементы, с -1 хэш-кодом.

Рабочий процесс веб-службы подразумевает, что в течение определенных элементов обработки добавляются, а затем удаляются из словаря (просто Add и Remove). Не ахти какое дело. Но кажется, что после удаления всех элементов словарь заполнен (null, null) KeyValuePair s. Тысячи из них на самом деле, так что они занимают большую часть памяти и, в конечном итоге, происходит переполнение, причем соответствующий принудительный пул приложений и DW20.exe получают все циклы CPU, которые он может получить.

Словарь фактически Dictionary<SomeKeyType, IEnumerable<KeyValuePair<SomeOtherKeyType, SomeCustomType>>> (System.OutOfMemoryException из-за большого словаря), поэтому я уже проверил, есть ли какая-то ссылка, содержащая вещи.

Словарь содержится в статическом объекте (чтобы сделать его доступным для разных потоков обработки через обработку), поэтому из этого вопроса и многих других (Статические члены когда-либо собирают мусор?) Я понимаю, почему этот словарь находится в Generation 2. Но это также является причиной этих (null, null)? Даже если я удаляю элементы из словаря, что-то будет всегда занято в памяти?

Это не проблема скорости, как в этом вопросе Освободить память от больших структур данных в С#. Кажется, память никогда не восстанавливается.

Есть ли что-то, что я могу сделать, чтобы фактически удалить элементы из словаря, а не просто заполнять его парами (null, null)? Есть еще что мне нужно проверить?

Ответ 1

Словари хранят элементы в хеш-таблице. Для этого используется внутренний массив. Из-за работы хэш-таблиц этот массив всегда должен быть больше фактического количества сохраненных элементов (по крайней мере, на 30% больше). Microsoft использует коэффициент загрузки 72%, то есть не менее 28% массива будет пустым (см. Обширное исследование структур данных с использованием С# 2.0 и особенно Класс System.Collections.Hashtable и Класс System.Collections.Generic.Dictionary) Поэтому нулевые/нулевые записи могут просто представлять это свободное пространство.

Если массив слишком мал, он будет расти автоматически; однако, когда элементы удаляются, массив не сокращается, но пространство, которое будет освобождено, должно быть повторно использовано при вставке новых элементов.

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

theDict = new Dictionary<TKey, IEnumerable<KeyValuePair<TKey2, TVal>>>(theDict);

Но проблема может возникнуть из фактических (непустых) записей. Ваш словарь статичен и поэтому никогда не будет автоматически исправляться сборщиком мусора, если вы не назначили ему другой словарь или null (theDict = new ... или theDict = null). Это справедливо только для самого словаря, который статичен, а не для его записей. Пока ссылки на удаленные записи существуют где-то в другом месте, они будут сохраняться. GC будет возвращать любой объект (ранее или позже), к которому нельзя получить доступ через какую-либо ссылку. Не имеет значения, был ли этот объект объявлен статическим или нет. Сами объекты не статичны, а только их ссылки.

Ответ 2

Похоже, вам нужно периодически перерабатывать пространство в этом dict. Вы можете сделать это, создав новый: new Dictionary<a,b>(oldDict). Не забудьте сделать это поточно-безопасным способом.

Когда это делать? Либо на отметке таймера (60 секунд?), Либо при возникновении определенного количества записей (100k?) (Вам нужно сохранить счетчик изменений).

Ответ 3

Решение может заключаться в вызове метода Clear() для статического словаря. Таким образом, ссылка на словарь останется доступной, но содержащиеся в ней объекты будут освобождены.

Ответ 4

Это похоже на проблему, которую я обнаружил при работе с большими объектами DataTable в приложении DB некоторое время назад. Просто вызов Clear в DataTable оставил все строки в памяти с нулевыми значениями. Таким образом, мы реализовали конкретный метод очистки, например:

someTable.Clear();
someTable.Dispose();
someTeble = new DataTable()...

с дополнительными шагами, чтобы добавить правильные столбцы обратно в новый пустой, чистый, DataTable.

Проклятие в этом было из c, что у нас было много кода, ссылающегося на someTable, который получал доступ, и код не указывал на новый экземпляр. Большой беспорядок.

Моя догадка заключается в том, что под капотом это неуправляемые объекты с оболочкой .net поверх них, и память освобождается только при уничтожении основного объекта.

Итак, если вы используете их динамически, вы должны сами очистить память.

Я просто столкнулся с этим сообщением, потому что я вижу ту же проблему с hashtables.