Лучшая практика принудительной сборки мусора в C#
по моему опыту кажется, что большинство людей скажут вам, что неразумно принудительно собирать мусор, но в некоторых случаях, когда вы работаете с большими объектами, которые не всегда собираются в поколении 0, но где проблема с памятью, нормально ли принудительно собирать? Есть ли лучшая практика для этого?
15 ответов:
лучше всего не форсировать сбор мусора.
согласно MSDN:
"можно заставить мусор коллекция звонит, но большую часть времени, это должно быть избежать, потому что это может создать проблема производительности. "
однако, если вы можете надежно проверить свой код, чтобы подтвердить, что вызов Collect () не окажет негативного влияния, тогда продолжайте...
просто попробуйте убедиться, что объекты убираются, когда они вам больше не нужны. Если у вас есть пользовательские объекты, посмотрите на использование "using statement" и интерфейса IDisposable.
по этой ссылке есть несколько хороших практических советов касаемо освобождая память / вывоз мусора и т. д.:
лучше всего не форсировать сборку мусора в большинстве случаев. (каждая система, над которой я работал, имела принудительные сборки мусора, подчеркивала проблемы, которые, если бы они были решены, удалили бы необходимость принудительной сборки мусора и значительно ускорили работу системы.)
здесь несколько случаев, когда вы узнайте больше об использовании памяти, чем сборщик мусора. Это вряд ли будет верно для нескольких пользователей приложение или служба, которая отвечает на несколько запросов одновременно.
однако в некоторых пакетная обработка типа вы знаете больше, чем GC. Например, рассмотрим приложение, которое.
- дан список имен файлов в командной строке
- обрабатывает один файл, а затем записывает результат в файл результатов.
- при обработке файла, создает множество взаимосвязанных объектов, которые не могут быть собраны до обработка файла завершена (например, дерево разбора)
- не сохраняет состояние соответствия между файлами, которые он обработал.
вы мая быть в состоянии сделать случай (после тщательного) тестирования, что вы должны заставить полную сборку мусора после обработки каждого файла.
другой случай-это сервис, который просыпается каждые несколько минут для обработки некоторых элементов, и не сохраняет никакого состояния, пока это спит. Затем заставляя полную коллекцию непосредственно перед сном мая стоит.
единственный раз, когда я бы подумал о принуждении коллекция-это когда я знаю, что много объекта были созданы недавно и очень мало объектов в настоящее время упоминаемый.
Я бы предпочел иметь API для сбора мусора, когда я мог бы дать ему подсказки об этом типе вещей без необходимости заставлять GC себя.
Смотрите также "выступление Рико Мариани лакомые кусочки"
посмотрите на это таким образом - это более эффективно выбрасывать кухонный мусор, когда мусорный бак находится на 10% или пусть он заполнится, прежде чем вынимать его?
не давая ему заполнить, вы тратите свое время, идя к и от мусорного бака снаружи. Это аналогично тому, что происходит при запуске потока GC - все управляемые потоки приостанавливаются во время его работы. И если я не ошибаюсь, поток GC может быть разделен между несколькими доменами приложений, поэтому сборка мусора влияет все они.
конечно, вы можете столкнуться с ситуацией, когда вы ничего не будете добавлять в мусорное ведро в ближайшее время - скажем, если вы собираетесь взять отпуск. Тогда было бы неплохо выбросить мусор перед выходом.
Это может быть один раз, когда принуждение GC может помочь - если ваша программа простаивает, используемая память не собирается в мусор, потому что нет никаких выделений.
Я думаю, что пример, приведенный Рико Мариани хорошо: это может быть целесообразным, чтобы вызвать GC если есть значительное изменение в состоянии приложения. Например, в Редакторе документов может быть нормально запускать GC при закрытии документа.
есть несколько общих принципов в программировании, которые являются абсолютными. В половине случаев, когда кто-то говорит: "Вы делаете это неправильно", они просто извергают определенное количество догм. В C это был страх перед такими вещами, как самоизменяющийся код или потоки, в языках GC он заставляет GC или, альтернативно, предотвращает запуск GC.
Как и в случае с большинством руководящих принципов и хороших эмпирических правил (и хороших практик проектирования), есть редкие случаи, когда это делает смысл работать в обход установленной нормы. Вы должны быть очень уверены, что понимаете ситуацию, что ваш случай действительно требует отмены обычной практики, и что вы понимаете риски и побочные эффекты, которые вы можете вызвать. Но бывают и такие случаи.
задачи программирования очень разнообразны и требуют гибкого подхода. Я видел случаи, когда имеет смысл блокировать GC на языках сбора мусора и местах, где имеет смысл запускать его, а не ждать его происходить естественным образом. В 95% случаев любой из них был бы признаком того, что он не подошел к проблеме правильно. Но 1 раз в 20, вероятно, есть действительный случай, чтобы сделать для него.
Я научился не пытаться перехитрить сборку мусора. С учетом сказанного, я просто придерживаюсь использования
usingключевое слово при работе с неуправляемыми ресурсами, такими как файловый ввод-вывод или подключения к базе данных.
один случай, с которым я недавно столкнулся, требовал ручных вызовов
GC.Collect()был при работе с большими объектами C++, которые были обернуты в крошечные управляемые объекты C++, которые, в свою очередь, были доступны из C#.сборщик мусора никогда не вызывался, потому что объем используемой управляемой памяти был незначительным, но объем используемой неуправляемой памяти был огромным. Вызов вручную
Dispose()для объектов потребуется, чтобы я отслеживал, когда объекты больше не нужны сами, тогда как звонюGC.Collect()очистит любые объекты, которые больше не упоминается.....
Не уверен, что это лучшая практика, но при работе с большим количеством изображений в цикле (т. е. создание и удаление большого количества графических/графических/растровых объектов) я регулярно позволяю GC.Собирать.
Я думаю, что где-то читал, что GC работает только тогда, когда программа (в основном) простаивает, а не в середине интенсивного цикла, так что это может выглядеть как область, где ручной GC может иметь смысл.
Я думаю, что вы уже перечислили лучшие практики, и это не использовать его, если это действительно необходимо. Я настоятельно рекомендую взглянуть на ваш код более подробно, используя инструменты профилирования потенциально, если это необходимо, чтобы ответить на эти вопросы в первую очередь.
- есть ли у вас что-то в коде, который объявляет элементы в большем объеме, чем необходимо
- использование памяти действительно слишком высоко
- сравнить производительность до и после использования GC.Соберите (), чтобы увидеть, если это действительно помогает.
предположим, что ваша программа не имеет утечки памяти, объекты накапливаются и не могут быть GC-ed в Gen 0, потому что: 1) они ссылаются на долгое время, поэтому попадают в Gen1 & Gen2; 2) они являются большими объектами (>80K), поэтому попадают в LOH (большая куча объектов). И LOH не делает уплотнение, как в Gen0, Gen1 & Gen2.
Проверьте счетчик производительности ".NET Memory" вы можете видеть, что 1) проблема действительно не проблема. Как правило, каждые 10 Gen0 GC запускают 1 Gen1 GC, и каждые 10 Gen1 и Gen2 в GC сработает 1 ГК. Теоретически GC1 и GC2 никогда не могут быть GC-ed, если нет давления на GC0 (если использование памяти программы действительно подключено). Со мной такого никогда не бывает.
для Проблемы 2) Вы можете проверить счетчик производительности ".NET Memory", чтобы проверить, не раздувается ли LOH. Если это действительно проблема для вашей проблемы, возможно, вы можете создать пул больших объектов, как это предлагает этот блог http://blogs.msdn.com/yunjin/archive/2004/01/27/63642.aspx.
большие объекты выделяются на LOH (большая куча объектов), а не на gen 0. Если вы говорите, что они не собирают мусор с gen 0, вы правы. Я считаю, что они собираются только тогда, когда происходит полный цикл GC (поколения 0, 1 и 2).
Это, как говорится, Я считаю, что с другой стороны GC будет регулировать и собирать память более агрессивно, когда вы работаете с большими объектами и давление памяти растет.
трудно сказать, собирать или нет и при каких обстоятельствах. Я раньше делал GC.Соберите () после удаления диалоговых окон / форм с многочисленными элементами управления и т. д. (потому что к тому времени, когда форма и ее элементы управления попадают в gen 2 из - за создания многих экземпляров бизнес-объектов/загрузки большого количества данных-никаких больших объектов, очевидно), но на самом деле не заметил никаких положительных или отрицательных эффектов в долгосрочной перспективе, сделав это.
Я хотел бы добавить, что: Звоню в ГК.Сбор () (+WaitForPendingFinalizers ()) является одной из частей истории. Как справедливо отметили другие, GC.COllect () является недетерминированной коллекцией и оставляется на усмотрение самого GC (CLR). Даже если вы добавляете вызов WaitForPendingFinalizers, он не может быть детерминированным. Возьмите код из этого msdn ссылке и запустите код с итерацией цикла объекта как 1 или 2. Вы найдете, что означает недетерминированный (установите точку останова в деструкторе объекта). Точно, деструктор не вызывается, когда было только 1 (или 2) задерживающихся объектов по ожиданию..().[Цитата reqd.]
Если ваш код имеет дело с неуправляемыми ресурсами (например, внешние дескрипторы файлов), необходимо реализовать деструкторы (или финализаторы).
вот интересный пример:
Примечание: если вы уже пробовали приведенный выше пример из MSDN, следующий код будет очищать воздух.
class Program { static void Main(string[] args) { SomePublisher publisher = new SomePublisher(); for (int i = 0; i < 10; i++) { SomeSubscriber subscriber = new SomeSubscriber(publisher); subscriber = null; } GC.Collect(); GC.WaitForPendingFinalizers(); Console.WriteLine(SomeSubscriber.Count.ToString()); Console.ReadLine(); } } public class SomePublisher { public event EventHandler SomeEvent; } public class SomeSubscriber { public static int Count; public SomeSubscriber(SomePublisher publisher) { publisher.SomeEvent += new EventHandler(publisher_SomeEvent); } ~SomeSubscriber() { SomeSubscriber.Count++; } private void publisher_SomeEvent(object sender, EventArgs e) { // TODO: something string stub = ""; } }Я предлагаю, сначала проанализировать, что выход может быть, а затем запустить, а затем прочитать причину ниже:
{деструктор вызывается неявно только после завершения программы. } Для детерминированной очистки объекта необходимо реализовать IDisposable и сделать явный вызов Dispose(). Вот в чем суть! :)
еще одна вещь, запуск GC Collect явно не может улучшить производительность вашей программы. Вполне возможно сделать его еще хуже.
.NET GC хорошо спроектирован и настроен на адаптивность, что означает, что он может регулировать порог GC0/1/2 в соответствии с "привычкой" использования памяти вашей программы. Таким образом, он будет адаптирован к вашей программе через некоторое время. После того, как вы ссылаться на ГК.Соберите явно, пороги будут сброшены! И .NET должен потратить время, чтобы адаптироваться к вашим опять "привычка" программы.
мое предложение всегда доверять .NET GC. Любые проблемы с памятью, проверьте счетчик производительности ".NET Memory" и диагностируйте мой собственный код.
Не уверен, что это лучшая практика...
предложение: не реализуйте это или что-нибудь, когда не уверены. Переоцените, когда факты известны, а затем выполните до/после тестов производительности для проверки.
однако, если вы можете надежно проверить свой код, чтобы подтвердить, что вызов Collect() не окажет негативного влияния, тогда продолжайте...
ИМХО, это похоже на высказывание " если вы можете доказать, что ваша программа никогда не будет иметь никаких ошибок в будущем, то вперед..."
со всей серьезностью, принуждение GC полезно для целей отладки/тестирования. Если вы чувствуете, что вам нужно сделать это в любое другое время, то либо вы заблуждаетесь, либо ваша программа была построена неправильно. В любом случае, решение не вынуждает GC...
Comments