Когда можно позвонить в GC.Collect?


166

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

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

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

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


Ответы:


153

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

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

ОБНОВЛЕНИЕ 2.7.2018

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

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

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

Видеть:


8
Согласно исходному коду MS, вызывающему GC.Collect (2) каждые 850 мс - это нормально. Не веришь? Тогда просто посмотрите PresentationCore.dll, MS.Internal.MemoryPressure.ProcessAdd (). В настоящее время у меня есть приложение для обработки изображений (маленькие изображения, ничего с реальной нагрузкой на память), где вызов GC.Collect (2) занимает больше 850 мс, и поэтому все приложение зависает (приложение тратит 99,7% времени в GC).
springy76

36
@ springy76: Работа Microsoft в одном месте не означает, что те, кто дает советы от Microsoft , считают это хорошим делом ...
Джон Скит,

4
Мне не нравится этот пример. Какой смысл делать это после закрытия формы? Один хороший пример, который я вижу, - после загрузки игрового уровня на XBox или WindowsPhone. На этих платформах GC запускается после выделения 1 МБ или чего-то подобного. Так что хорошо распределить как можно больше во время загрузки уровня (показывая какой-нибудь экран-заставку), а затем выполните GC.Collect, чтобы попытаться избежать коллекций во время игры.
Петр Перак

8
@Peri: смысл делать это после закрытия формы в том, что вы только что сделали несколько объектов (элементов управления, отображаемых данных) пригодными для сбора мусора - поэтому, вызывая, GC.Collectвы в основном сообщаете сборщику мусора, что вы знаете, лучше, чем это для разнообразия. Почему тебе не нравится пример?
Джон Скит

6
@SHCJ: GC.Collect()запросит, чтобы GC выполнил полный сбор. Если вы знаете, что только что сделали много ранее долгоживущих объектов, подходящих для сбора мусора, и считаете, что пользователь с меньшей вероятностью заметит небольшую паузу сейчас, чем позже, кажется вполне разумным думать, что сейчас лучше время, чтобы побудить коллекцию, чем позволить этому произойти позже.
Джон Скит

50

Я использую 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


1
И я, вероятно, добавлю также «WaitForPendingFinalizers» (или что-то еще) в этом случае ;-p
Марк

29

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

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

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

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

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

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

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

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

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



11

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

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

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

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

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


Я видел двухпроходную коллекцию в нескольких местах сейчас, но после прочтения отрывка в документации MSDN для GC.WaitForPendingFinalizers, который говорит: «Дождитесь завершения всех финализаторов, прежде чем продолжить. Без этого вызова GC.WaitForPendingFinalizers, рабочий цикл ниже может выполняться одновременно с финализаторами. При этом вызове рабочий цикл выполняется только после вызова всех финализаторов. " Я просто параноик. Вы знаете точный источник для двух проходов?
Jerhewet

1
@jerhewet: ключ к пониманию необходимости двух коллекций - в понимании того, что происходит с объектами с финализаторами. К сожалению, я не знаю точно, что вы просите, но прочитал эту статью и этот вопрос на SO .
Пол Руане

10

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

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

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


8

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


7

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

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


3
Уже был там. Я не пытаюсь найти оправдания тому, что вы не должны делать, но я хотел бы знать, есть ли конкретные случаи, когда это будет приемлемо.
Брайан Расмуссен

5

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

При программировании с использованием Interop каждый отдельный COM-объект должен быть освобожден вручную, обычно с использованием Marshal.ReleseComObject (). Кроме того, вызов Garbage Collection вручную может помочь немного «очиститься». Вызов следующего кода, когда вы закончите с объектами Interop, кажется, очень помогает:

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

По моему личному опыту, использование комбинации ReleaseComObject и ручного вызова сборки мусора значительно сокращает использование памяти продуктами Office, в частности Excel.


да, я столкнулся с этим при доступе к .net Excel, который также работает через com-объекты. Важно отметить, что это не будет хорошо работать в режиме отладки, потому что операции GC там ограничены. Он будет работать по назначению только в режиме RELEASE. соответствующая ссылка: stackoverflow.com/questions/17130382/…
Blechdose

5

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

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();

Почему отказано в голосовании? Мой ответ - пример, который демонстрирует, когда звонить в GC. У вас есть лучшее предложение? Приглашаем вас представить.
Даниэль Б

4

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

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

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

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

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


3

Одно полезное место для вызова GC.Collect () - это модульный тест, когда вы хотите убедиться, что вы не создаете утечку памяти (например, если вы что-то делаете с WeakReferences или ConditionalWeakTable, динамически генерируемым кодом и т. Д.).

Например, у меня есть несколько тестов, таких как:

WeakReference w = CodeThatShouldNotMemoryLeak();
Assert.IsTrue(w.IsAlive);
GC.Collect();
GC.WaitForPendingFinalizers();
Assert.IsFalse(w.IsAlive);

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




2
using(var stream = new MemoryStream())
{
   bitmap.Save(stream, ImageFormat.Png);
   techObject.Last().Image = Image.FromStream(stream);
   bitmap.Dispose();

   // Without this code, I had an OutOfMemory exception.
   GC.Collect();
   GC.WaitForPendingFinalizers();
   //
}

2

Есть ситуации, когда это лучше, чем потом сожалеть.

Здесь одна ситуация.

Можно написать неуправляемую DLL в C #, используя переписывание IL (потому что есть ситуации, когда это необходимо).

Теперь предположим, например, что DLL создает массив байтов на уровне класса - потому что многие из экспортируемых функций нуждаются в таком доступе. Что происходит, когда DLL выгружается? В этот момент автоматически вызывается сборщик мусора? Я не знаю, но, будучи неуправляемой DLL, вполне возможно, что GC не вызывается. И это было бы большой проблемой, если бы его не называли. Когда DLL выгружается, сборщик мусора тоже будет - так кто же будет отвечать за сбор любого возможного мусора и как они это сделают? Лучше использовать сборщик мусора в C #. Иметь функцию очистки (доступную для клиента DLL), в которой переменные уровня класса имеют значение null и вызывается сборщик мусора.

Береженого Бог бережет.


2

Я все еще не уверен в этом. Я работаю с 7 лет на сервере приложений. Наши большие установки используют оперативную память объемом 24 ГБ. Его многопоточный и ВСЕ вызовы для GC.Collect () столкнулись с действительно ужасными проблемами производительности.

Многие Сторонние Компоненты использовали GC.Collect (), когда они думали, что это было разумно сделать это прямо сейчас. Таким образом, простая группа Excel-отчетов блокировала сервер приложений для всех потоков несколько раз в минуту.

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

Но я также использую Серверы на Win32, и здесь я начал активно использовать GC.Collect () после получения исключения OutOfMemoryException.

Но я также довольно неуверен в этом, потому что я часто замечал, что когда я получаю OOM на 32-битной системе, и я снова пытаюсь запустить ту же операцию, не вызывая GC.Collect (), она просто работает нормально.

Одна вещь, которая меня интересует, это само исключение OOM ... Если бы я написал .Net Framework и не смог бы выделить блок памяти, я бы использовал GC.Collect (), дефрагментировал память (??), попробуйте снова , и если я все еще не могу найти свободный блок памяти, то я бы выбросил OOM-Exception.

Или, по крайней мере, сделайте это поведение настраиваемым параметром из-за недостатков производительности, связанных с GC.Collect.

Теперь в моем приложении много такого кода, чтобы «решить» проблему:

public static TResult ExecuteOOMAware<T1, T2, TResult>(Func<T1,T2 ,TResult> func, T1 a1, T2 a2)
{

    int oomCounter = 0;
    int maxOOMRetries = 10;
    do
    {
        try
        {
            return func(a1, a2);
        }
        catch (OutOfMemoryException)
        {
            oomCounter++;
            if (maxOOMRetries > 10)
            {
                throw;
            }
            else
            {
                Log.Info("OutOfMemory-Exception caught, Trying to fix. Counter: " + oomCounter.ToString());
                System.Threading.Thread.Sleep(TimeSpan.FromSeconds(oomCounter * 10));
                GC.Collect();
            }
        }
    } while (oomCounter < maxOOMRetries);

    // never gets hitted.
    return default(TResult);
}

(Обратите внимание, что поведение Thread.Sleep () действительно специфично для приложения, поскольку мы запускаем службу кэширования ORM, и службе требуется некоторое время для освобождения всех кэшированных объектов, если объем оперативной памяти превышает некоторые предопределенные значения. Поэтому он ожидает несколько секунд в первый раз, и увеличилось время ожидания при каждом появлении OOM.)


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

If i would have written the .Net Framework, and i can't alloc a memory block, i would use GC.Collect(),- Я думаю, что они уже делают это - я видел признаки того, что один из внутренних триггеров GC - определенные сбои в распределении памяти.
Г. Стойнев

2

Вам следует избегать использования GC.Collect (), так как он очень дорогой. Вот пример:

        public void ClearFrame(ulong timeStamp)
    {
        if (RecordSet.Count <= 0) return;
        if (Limit == false)
        {
            var seconds = (timeStamp - RecordSet[0].TimeStamp)/1000;
            if (seconds <= _preFramesTime) return;
            Limit = true;
            do
            {
                RecordSet.Remove(RecordSet[0]);
            } while (((timeStamp - RecordSet[0].TimeStamp) / 1000) > _preFramesTime);
        }
        else
        {
            RecordSet.Remove(RecordSet[0]);

        }
        GC.Collect(); // AVOID
    }

РЕЗУЛЬТАТ ИСПЫТАНИЯ: ИСПОЛЬЗОВАНИЕ ЦП 12%

Когда вы переходите на это:

        public void ClearFrame(ulong timeStamp)
    {
        if (RecordSet.Count <= 0) return;
        if (Limit == false)
        {
            var seconds = (timeStamp - RecordSet[0].TimeStamp)/1000;
            if (seconds <= _preFramesTime) return;
            Limit = true;
            do
            {
                RecordSet[0].Dispose(); //  Bitmap destroyed!
                RecordSet.Remove(RecordSet[0]);
            } while (((timeStamp - RecordSet[0].TimeStamp) / 1000) > _preFramesTime);
        }
        else
        {
            RecordSet[0].Dispose(); //  Bitmap destroyed!
            RecordSet.Remove(RecordSet[0]);

        }
        //GC.Collect();
    }

РЕЗУЛЬТАТ ТЕСТА: ИСПОЛЬЗОВАНИЕ ЦП 2-3%


1

Другая причина заключается в том, что SerialPort открыт на COM-порту USB, а затем USB-устройство отключено. Поскольку SerialPort был открыт, ресурс содержит ссылку на ранее подключенный порт в системном реестре. Системный реестр будет содержать устаревшие данные , поэтому список доступных портов будет неправильным. Поэтому порт должен быть закрыт.

Вызов SerialPort.Close () для порта вызывает Dispose () для объекта, но он остается в памяти до тех пор, пока сборка мусора не будет фактически запущена, в результате чего реестр останется устаревшим, пока сборщик мусора не решит освободить ресурс.

С https://stackoverflow.com/a/58810699/8685342 :

try
{
    if (port != null)
        port.Close(); //this will throw an exception if the port was unplugged
}
catch (Exception ex) //of type 'System.IO.IOException'
{
    System.GC.Collect();
    System.GC.WaitForPendingFinalizers();
}

port = null;

0

Это не имеет отношения к вопросу, но для XSLT-преобразований в .NET (XSLCompiledTranform) у вас может не быть выбора. Другим кандидатом является контроль MSHTML.



0

одна хорошая причина для вызова GC - на небольших компьютерах ARM с небольшим объемом памяти, таких как Raspberry PI (работает с моно). Если нераспределенные фрагменты памяти используют слишком много системной оперативной памяти, ОС Linux может работать нестабильно. У меня есть приложение, где я должен вызывать GC каждую секунду (!), Чтобы избавиться от проблем переполнения памяти.

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


0

Так как есть куча малых объектов (SOH) и куча больших объектов (LOH)

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

В .net4.5 мы также можем сжать LOH, используя большой режим сравнения объектов


0

Если вы создаете много новых System.Drawing.Bitmapобъектов, сборщик мусора не очищает их. В конечном итоге GDI + решит, что вам не хватает памяти, и выдаст исключение «Параметр не действителен». Звонки GC.Collect()так часто (не слишком часто!), Кажется, решают эту проблему.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.