Лучшая практика для принудительной сборки мусора в C #


118

По моему опыту кажется, что большинство людей скажет вам, что неразумно принудительно собирать мусор, но в некоторых случаях, когда вы работаете с большими объектами, которые не всегда собираются в поколении 0, но когда память является проблемой, это нормально, чтобы заставить собирать? Есть ли лучшая практика для этого?

Ответы:


112

Лучше всего не принудительно выполнять сборку мусора.

Согласно MSDN:

«Можно принудительно вызвать сборку мусора, вызвав Collect, но в большинстве случаев этого следует избегать, поскольку это может создать проблемы с производительностью».

Однако, если вы можете надежно протестировать свой код, чтобы подтвердить, что вызов Collect () не окажет отрицательного воздействия, продолжайте ...

Просто постарайтесь убрать предметы, когда они вам больше не нужны. Если у вас есть настраиваемые объекты, обратите внимание на использование оператора using и интерфейса IDisposable.

По этой ссылке есть несколько полезных практических советов по освобождению памяти / сборке мусора и т. Д .:

http://msdn.microsoft.com/en-us/library/66x5fx1b.aspx


3
Кроме того, вы можете установить другой параметр LatencyMode msdn.microsoft.com/en-us/library/bb384202.aspx
Дэвид

4
Если ваши объекты указывают на неуправляемую память, вы можете сообщить об этом сборщику мусора через GC.AddMemoryPressure Api ( msdn.microsoft.com/en-us/library/… ). Это дает сборщику мусора больше информации о вашей системе, не мешая алгоритмам сбора.
Govert

+1: * посмотрите на использование «оператора using» и интерфейса IDisposable. * Я бы даже не рассматривал форсирование, кроме как в крайнем случае - хороший совет (читаемый как «отказ от ответственности»). Однако я заставляю сборку в модульном тесте имитировать потерю активной ссылки в серверной операции - в конечном итоге бросая файл TargetOfInvocationNullException.
IAbstract

33

Взгляните на это с другой стороны - эффективнее ли выбросить кухонный мусор, когда мусорное ведро на 10%, или дать ему заполниться, прежде чем вынести его?

Не позволяя ему заполняться, вы тратите время на прогулки к мусорному ведру снаружи и обратно. Это аналогично тому, что происходит при запуске потока GC - все управляемые потоки приостанавливаются во время его работы. И если я не ошибаюсь, поток сборки мусора может быть разделен между несколькими доменами приложений, поэтому сборка мусора влияет на все из них.

Конечно, вы можете столкнуться с ситуацией, когда в ближайшее время вы не будете ничего добавлять в мусорное ведро - скажем, если вы собираетесь в отпуск. Тогда было бы неплохо выкинуть мусор перед выходом на улицу.

Это МОЖЕТ быть один раз, когда принудительный сборщик мусора может помочь - если ваша программа простаивает, используемая память не собирается сборщиком мусора, потому что нет выделений.


5
Если бы у вас был ребенок, который умер бы, если бы вы оставили его более чем на минуту, и у вас была бы только минута, чтобы справиться с мусором, тогда вам нужно было бы делать понемногу каждый раз, а не все сразу. К сожалению, чем чаще вы вызываете метод GC :: Collect (), тем быстрее он работает. Итак, для движка реального времени, если вы не можете просто использовать механизм удаления и позволить GC объединять ваши данные, вам не следует использовать управляемую систему - согласно моему ответу (вероятно, ниже этого, lol).
Джин

1
В моем случае я ПОВТОРНО использую алгоритм A * (кратчайший путь), чтобы настроить его производительность ... который в производственной среде будет запускаться только один раз (на каждой «карте»). Поэтому я хочу, чтобы сборщик мусора выполнялся перед каждой итерацией, за пределами моего «блока измерения производительности», потому что я чувствую, что это более точно моделирует ситуацию в производстве, в которой сборщик мусора может / должен быть принудительно принудительно выполнен после навигации по каждой «карте».
corlettk

Вызов GC перед измеренным блоком на самом деле не моделирует ситуацию в производственной среде, потому что в производственной среде GC будет выполняться в непредсказуемое время. Чтобы смягчить это, вы должны провести длительное измерение, которое будет включать в себя несколько выполнений GC, и учесть пики во время GC в вашем анализе и статистике.
Ран

32

Лучше всего в большинстве случаев не принудительно выполнять сборку мусора. (Каждая система, над которой я работал, которая имела принудительную сборку мусора, имела проблемы подчеркивания, которые, если бы они были решены, устранили бы необходимость принудительной сборки мусора и значительно ускорили работу системы.)

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

Однако в некоторой пакетной обработке вы знаете больше, чем GC. Например, рассмотрим приложение, которое.

  • Предоставляется список имен файлов в командной строке
  • Обрабатывает отдельный файл, а затем записывает результат в файл результатов.
  • При обработке файла создает множество взаимосвязанных объектов, которые не могут быть собраны до завершения обработки файла (например, дерево синтаксического анализа)
  • Не сохраняет много состояний между обработанными файлами .

Вы можете быть в состоянии сделать случай (после тщательного) тестирования , что вы должны заставить полный сбор мусора после того как вы обрабатывать каждый файл.

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

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

Я бы предпочел иметь API для сборки мусора, когда я мог бы давать ему подсказки об этом типе вещей, не заставляя себя выполнять сборку мусора.

См. Также « Лакомые кусочки выступления Рико Мариани »


2
Аналогия: Playschool (система) хранит цветные карандаши (ресурсы). В зависимости от количества детей (задач) и недостатка цветов учитель (.Net) решает, как распределять и делиться между детьми. Когда требуется редкий цвет, учитель может выделить его из пула или найти тот, который не используется. Учитель может по своему усмотрению периодически собирать неиспользованные мелки (сбор мусора), чтобы поддерживать порядок (оптимизировать использование ресурсов). Как правило, родитель (программист) не может заранее определить лучшую политику уборки в классе. Запланированный сон одного ребенка вряд ли станет подходящим моментом, чтобы помешать окраске других детей.
AlanK

1
@AlanK, мне это нравится, потому что, когда дети уходят домой на день, это очень хорошее время для помощника учителя, чтобы сделать хорошую уборку, чтобы дети не мешали. (Последние системы, над которыми я работал, только что перезапускали процесс обслуживания в такие моменты при принудительном сборке мусора.)
Ян Рингроуз

21

Я думаю, что пример, приведенный Рико Мариани, был хорош: может быть уместно запускать сборщик мусора, если есть существенное изменение в состоянии приложения. Например, в редакторе документов может быть нормально запускать сборщик мусора при закрытии документа.


2
Или непосредственно перед открытием большого непрерывного объекта, который показал историю сбоев и не предлагает эффективного решения для увеличения его детализации.
crokusek

17

Есть несколько общих правил программирования, которые являются абсолютными. В половине случаев, когда кто-то говорит: «Вы делаете это неправильно», они просто изливают определенную догму. В C раньше было боязнь таких вещей, как самомодифицирующийся код или потоки, на языках GC это заставляет GC или, альтернативно, предотвращает запуск GC.

Как и в случае с большинством руководящих принципов и хороших практических правил (и хороших практик проектирования), в редких случаях имеет смысл обойти установленную норму. Вы должны быть очень уверены, что понимаете дело, что ваш случай действительно требует отказа от общепринятой практики и что вы понимаете риски и побочные эффекты, которые вы можете вызвать. Но есть такие случаи.

Проблемы программирования очень разнообразны и требуют гибкого подхода. Я видел случаи, когда имеет смысл блокировать сборщик мусора на языках со сборкой мусора, и в местах, где имеет смысл запускать его, а не ждать, пока это произойдет естественным образом. В 95% случаев любой из этих факторов может быть признаком неправильного подхода к проблеме. Но 1 раз из 20, вероятно, есть веские основания для этого.


12

Я научился не пытаться перехитрить сборку мусора. С учетом сказанного, я просто использую usingключевое слово при работе с неуправляемыми ресурсами, такими как файловый ввод-вывод или соединения с базой данных.


27
компилятор? какое отношение компилятор имеет к GC? :)
KristoferA

1
Ничего, только компилирует и оптимизирует код. И это определенно не имеет ничего общего с CLR ... или даже .NET.
Кон

1
Объекты, занимающие много памяти, такие как очень большие изображения, могут не получить сборщик мусора, если вы явно не собираете их. Я думаю, что это (большие объекты) было проблемой OP, более или менее.
code4life

1
Обертывание в using гарантирует, что он будет запланирован для GC, как только он выйдет из области использования. Если компьютер не взорвется, эта память, скорее всего, будет очищена.
Кон

9

Не уверен, что это лучшая практика, но при работе с большим количеством изображений в цикле (т. Е. При создании и удалении большого количества объектов Graphics / Image / Bitmap) я регулярно разрешаю GC.Collect.

Думаю, я где-то читал, что сборщик мусора работает только тогда, когда программа (в основном) простаивает, а не в середине интенсивного цикла, так что это может выглядеть как область, где ручной сбор мусора может иметь смысл.


Вы уверены, что вам это нужно? GC будет собирать, если ему нужна память, даже если ваш код не простаивает.
Конрад Рудольф,

Не уверен, как это сейчас в .net 3.5 SP1, но ранее (1.1, и я считаю, что я тестировал 2.0) это действительно повлияло на использование памяти. ГХ, конечно, всегда будет собирать, когда это необходимо, но вы все равно можете потратить 100 мегабайт оперативной памяти, когда вам нужно всего 20. Однако потребуется еще несколько тестов
Майкл Штум

2
Сборщик мусора запускается при выделении памяти, когда поколение 0 достигает определенного порога (например, 1 МБ), а не когда «что-то неактивно». В противном случае вы могли бы закончить с OutOfMemoryException в цикле, просто выделяя и немедленно отбрасывая объекты.
liggett78,

5
100 мегабайт оперативной памяти не тратятся впустую, если это не требуется другим процессам. Это дает вам хороший прирост производительности :-P
Orion Edwards

9

Один случай, с которым я недавно столкнулся, требовал ручных вызовов, GC.Collect()был при работе с большими объектами C ++, которые были заключены в крошечные управляемые объекты C ++, которые, в свою очередь, были доступны из C #.

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


6
Лучший способ решить эту проблему - вызвать GC.AddMemoryPressure (ApproximateSizeOfUnmanagedResource)конструктор, а затем GC.RemoveMemoryPressure(addedSize)- финализатор. Таким образом, сборщик мусора будет запускаться автоматически с учетом размера неуправляемых структур, которые могут быть собраны. stackoverflow.com/questions/1149181/…
HugoRune

И еще лучший способ решить эту проблему - вызвать Dispose (), что вы должны делать в любом случае.
fabspro

2
Лучший способ - использовать структуру Using. Попробуйте / Наконец .Dispose - это
проблема

7

Я думаю, что вы уже перечислили лучшие практики, и их НЕ следует использовать, если ДЕЙСТВИТЕЛЬНО не требуется. Я настоятельно рекомендую изучить ваш код более подробно, потенциально используя инструменты профилирования, чтобы сначала ответить на эти вопросы.

  1. Есть ли в вашем коде что-то, что объявляет элементы в большем объеме, чем необходимо?
  2. Действительно ли использование памяти слишком велико
  3. Сравните производительность до и после использования GC.Collect (), чтобы увидеть, действительно ли это помогает.

5

Предположим, ваша программа не имеет утечки памяти, объекты накапливаются и не могут быть объединены в сборку мусора в Gen 0, потому что: 1) На них ссылаются в течение длительного времени, поэтому перейдите в Gen1 и Gen2; 2) Это большие объекты (> 80K), поэтому перейдите в LOH (Large Object Heap). И LOH не выполняет уплотнение, как в Gen0, Gen1 и Gen2.

Проверьте счетчик производительности «.NET Memory», и вы увидите, что 1) проблема действительно не является проблемой. Как правило, каждые 10 GC Gen0 запускают 1 GC Gen1, а каждые 10 GC Gen1 запускают 1 GC Gen2. Теоретически GC1 и GC2 никогда не могут быть GC-ed, если нет давления на GC0 (если использование памяти программы действительно подключено). Со мной такого никогда не бывает.

Для проблемы 2) вы можете проверить счетчик производительности «Память .NET», чтобы проверить, не раздувается ли LOH. Если это действительно проблема для вашей проблемы, возможно, вы можете создать пул больших объектов, как предлагается в этом блоге http://blogs.msdn.com/yunjin/archive/2004/01/27/63642.aspx .


4

Большие объекты размещаются в LOH (куча больших объектов), а не в поколении 0. Если вы говорите, что они не собираются сборщиком мусора для поколения 0, вы правы. Я считаю, что они собираются только тогда, когда происходит полный цикл сборки мусора (поколения 0, 1 и 2).

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

Сложно сказать, собирать или нет и при каких обстоятельствах. Раньше я делал GC.Collect () после удаления диалоговых окон / форм с многочисленными элементами управления и т. Д. (Потому что к тому времени, когда форма и ее элементы управления попадают в поколение 2 из-за создания множества экземпляров бизнес-объектов / загрузки большого количества данных - нет очевидно, большие объекты), но на самом деле не заметил никаких положительных или отрицательных эффектов в долгосрочной перспективе.


4

Я хотел бы добавить, что: вызов GC.Collect () (+ WaitForPendingFinalizers ()) - это одна часть истории. Как справедливо упоминалось другими, GC.COllect () является недетерминированной коллекцией и оставлено на усмотрение самого GC (CLR). Даже если вы добавите вызов WaitForPendingFinalizers, он не может быть детерминированным. Возьмите код из этой ссылки msdn и запустите код с итерацией цикла объекта как 1 или 2. Вы обнаружите, что означает недетерминированный (установите точку останова в деструкторе объекта). Точнее, деструктор не вызывается, когда Wait .. () содержит только 1 (или 2) устаревших объекта. [Треб. Цитирования]

Если ваш код имеет дело с неуправляемыми ресурсами (например, дескрипторами внешних файлов), вы должны реализовать деструкторы (или финализаторы).

Вот интересный пример:

Примечание . Если вы уже пробовали приведенный выше пример из 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 (). В этом суть! :)


2

Еще одна вещь: явный запуск GC Collect НЕ может улучшить производительность вашей программы. Вполне возможно сделать хуже.

.NET GC хорошо спроектирован и настроен для адаптации, что означает, что он может регулировать порог GC0 / 1/2 в соответствии с «привычкой» использования памяти вашей программой. Таким образом, через некоторое время он будет адаптирован к вашей программе. Как только вы явно вызовете GC.Collect, пороги будут сброшены! И .NET придется потратить время, чтобы снова адаптироваться к «привычке» вашей программы.

Я предлагаю всегда доверять .NET GC. Любые проблемы с памятью обнаруживаются, проверяйте счетчик производительности ".NET Memory" и диагностируйте мой собственный код.


6
Я думаю, что лучше объединить этот ответ со своим предыдущим.
Salamander2007,

1

Не уверен, что это лучшая практика ...

Предложение: не выполняйте это или что-либо еще, если не уверены. Переоценивайте, когда факты известны, а затем выполняйте тесты производительности до / после для проверки.


0

Однако, если вы можете надежно протестировать свой код, чтобы подтвердить, что вызов Collect () не окажет отрицательного воздействия, продолжайте ...

ИМХО, это похоже на высказывание «Если вы можете доказать, что ваша программа никогда не будет содержать ошибок в будущем, тогда вперед ...»

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


«тогда либо вы ошибаетесь, либо ваша программа была построена неправильно. В любом случае решение не заставляет сборщик мусора ...» Абсолюты почти всегда неверны. В некоторых исключительных обстоятельствах это имеет смысл.
роллы
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.