Когда можно позвонить в GC.Collect? - PullRequest
154 голосов
/ 25 января 2009

Общий совет заключается в том, что вы не должны вызывать GC.Collect из своего кода, но каковы исключения из этого правила?

Я могу вспомнить только несколько очень конкретных случаев, когда имеет смысл форсировать сборку мусора.

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

Есть ли другие случаи, когда допустимо звонить GC.Collect?

Ответы [ 22 ]

144 голосов
/ 25 января 2009

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

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

ОБНОВЛЕНИЕ 2.7.2018

Начиная с .NET 4.5 - есть GCLatencyMode.LowLatency и GCLatencyMode.SustainedLowLatency. При входе и выходе из любого из этих режимов рекомендуется принудительно выполнить полный сбор данных с помощью GC.Collect(2, GCCollectionMode.Forced).

.

Начиная с .NET 4.6 - существует метод GC.TryStartNoGCRegion (используется для установки значения только для чтения GCLatencyMode.NoGCRegion). Это может само по себе выполнить полную блокировку сборки мусора в попытке освободить достаточно памяти, но, учитывая, что мы не разрешаем сборщик мусора на некоторое время, я бы сказал, что это также хорошая идея - выполнить сборку мусора до и после.

Источник: инженер Microsoft Бен Уотсон: Написание высокопроизводительного кода .NET , 2-е изд. 2018.

См:

47 голосов
/ 25 января 2009

Я использую GC.Collect только при написании грубых тестов производительности / профилировщика; у меня есть два (или более) блока кода для тестирования - что-то вроде:

GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
TestA(); // may allocate lots of transient objects
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
TestB(); // may allocate lots of transient objects
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
...

Так что TestA() и TestB() работают с максимально похожим состоянием, т. Е. TestB() не забивается только потому, что TestA оставил его очень близко к точке перелома.

Классическим примером может быть простой консольный exe (Main метод, достаточно сортируемый для публикации здесь, например), который показывает разницу между конкатенацией циклических строк и StringBuilder.

Если мне нужно что-то точное, то это будут два совершенно независимых теста - но часто этого достаточно, если мы просто хотим минимизировать (или нормализовать) GC во время тестов, чтобы получить грубое представление о поведении.

Во время производственного кода? Я еще не использовал его; -p

29 голосов
/ 24 сентября 2009

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

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

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

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

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

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

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

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

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

18 голосов
/ 25 января 2009

Один случай, когда вы пытаетесь выполнить модульное тестирование кода, который использует WeakReference .

9 голосов
/ 22 марта 2011

В больших системах 24/7 или 24/6 - системах, которые реагируют на сообщения, запросы RPC или которые непрерывно опрашивают базу данных или обрабатывают - полезно иметь способ выявления утечек памяти. Для этого я склонен добавлять в приложение механизм, который временно приостанавливает любую обработку, а затем выполняет полную сборку мусора. Это переводит систему в состояние покоя, где оставшаяся память либо является законно долгоживущей памятью (кэши, конфигурация и т. Д.), Либо «протекает» (объекты, которые не ожидаются или не желают быть укорененными, но на самом деле есть). *

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

Чтобы быть уверенным, что вы получите весь мусор, вам нужно выполнить две коллекции:

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

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

8 голосов
/ 18 октября 2013

Как решение для фрагментации памяти. Я получал исключения из памяти при записи большого количества данных в поток памяти (чтение из сетевого потока). Данные были записаны в 8K кусках. После достижения 128M было исключение, хотя было много доступной памяти (но она была фрагментирована). Вызов GC.Collect () решил проблему. Я смог справиться с 1G после исправления.

8 голосов
/ 25 января 2009

Вы можете вызвать GC.Collect (), когда знаете что-то о природе приложения, которого не знает сборщик мусора. Заманчиво думать, что, как автор, это очень вероятно. Однако правда в том, что GC представляет собой довольно хорошо написанную и протестированную экспертную систему, и редко вы узнаете что-нибудь о низкоуровневых путях кода, которых нет.

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

Однако, в большинстве случаев GC достаточно умен, чтобы все равно это делать.

7 голосов
/ 25 января 2009

Взгляните на эту статью Рико Мариани. Он дает два правила, когда вызывать GC.Collect (правило 1: «Не надо»):

Когда вызывать GC.Collect ()

5 голосов
/ 08 сентября 2017

Я проводил тестирование производительности для массива и списка:

private static int count = 100000000;
private static List<int> GetSomeNumbers_List_int()
{
    var lstNumbers = new List<int>();
    for(var i = 1; i <= count; i++)
    {
        lstNumbers.Add(i);
    }
    return lstNumbers;
}
private static int[] GetSomeNumbers_Array()
{
    var lstNumbers = new int[count];
    for (var i = 1; i <= count; i++)
    {
        lstNumbers[i-1] = i + 1;
    }
    return lstNumbers;
}
private static int[] GetSomeNumbers_Enumerable_Range()
{
    return  Enumerable.Range(1, count).ToArray();
}

static void performance_100_Million()
{
    var sw = new Stopwatch();

    sw.Start();
    var numbers1 = GetSomeNumbers_List_int();
    sw.Stop();
    //numbers1 = null;
    //GC.Collect();
    Console.WriteLine(String.Format("\"List<int>\" took {0} milliseconds", sw.ElapsedMilliseconds));

    sw.Reset();
    sw.Start();
    var numbers2 = GetSomeNumbers_Array();
    sw.Stop();
    //numbers2 = null;
    //GC.Collect();
    Console.WriteLine(String.Format("\"int[]\" took {0} milliseconds", sw.ElapsedMilliseconds));

    sw.Reset();
    sw.Start();
//getting System.OutOfMemoryException in GetSomeNumbers_Enumerable_Range method
    var numbers3 = GetSomeNumbers_Enumerable_Range();
    sw.Stop();
    //numbers3 = null;
    //GC.Collect();

    Console.WriteLine(String.Format("\"int[]\" Enumerable.Range took {0} milliseconds", sw.ElapsedMilliseconds));
}

и я получил OutOfMemoryException в методе GetSomeNumbers_Enumerable_Range, единственный обходной путь - освободить память следующим образом:

numbers = null;
GC.Collect();
4 голосов
/ 25 января 2009

В вашем примере, я думаю, что вызов GC.Collect не проблема, а скорее проблема дизайна.

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

Таким образом, вам не нужно беспокоиться о вызове GC.Collect (что вы должны редко , если вообще нужно делать).

При этом у Рико Мариани есть отличная запись в блоге на эту тему, которую можно найти здесь:

http://blogs.msdn.com/ricom/archive/2004/11/29/271829.aspx

...