Коли прийнятно викликати 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). Це може само собою виконати повне блокування збору сміття, намагаючись звільнити достатню кількість пам’яті, але, враховуючи, що ми забороняємо GC на певний період, я можу стверджувати, що це також гарна ідея виконати повний GC до і після.

Джерело: Інженер 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 працює після виділення 1MB або sth подібного. Тому добре виділити стільки, скільки зможете під час завантаження рівня (показуючи деякий екран сплеску), а потім зробити 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залишив його дуже близько до точки перекидання.

Класичним прикладом може бути проста консолідація Mainконсолінованих рядків та StringBuilder.

Якщо мені потрібно щось точне, то це були б два абсолютно незалежні тести - але часто цього достатньо, якщо ми просто хочемо мінімізувати (або нормалізувати) ГК під час тестів, щоб отримати грубе відчуття поведінки.

Під час виробничого коду? Я ще маю це використовувати ;-p


1
І я, мабуть, додаю "WaitForPendingFinalizers" (або що б там не було) у цьому випадку
;-p

29

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

Є кілька випадків, коли ви знаєте більше про використання пам’яті, а потім збирач сміття. Це навряд чи може бути правдою для багатокористувацької програми або сервісу, який відповідає на більше ніж один запит.

Однак у деяких обробках типу партії ви знаєте більше, ніж GC. Наприклад, розгляньте програму, яка.

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

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

Інший випадок - це служба, яка прокидається кожні кілька хвилин, щоб обробити деякі предмети, і не тримає жодного стану, поки вона спить . Тоді примушувати повну колекцію безпосередньо перед сном, можливо, варто.

Єдиний раз, коли я б розглядав питання про форсування колекції, - це коли я знаю, що нещодавно було створено багато об’єктів, і на даний момент дуже мало об'єктів.

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

Дивіться також " Підказки виступу Ріко Маріані "



11

У великих системах 24/7 або 24/6 - системах, які реагують на повідомлення, запити RPC або які запитують базу даних або обробляють постійно - корисно мати спосіб ідентифікації витоків пам'яті. Для цього я, як правило, додаю механізм до програми, щоб тимчасово призупинити будь-яку обробку і потім здійснити повне вивезення сміття. Це ставить систему в спокійний стан, коли пам’ять, що залишилася, або законно довго зберігається пам'ять (кеші, конфігурація та ін.), Або ще "просочена" (об'єкти, які не очікуються або бажають вкорінюватись, але насправді є).

Наявність цього механізму значно спрощує профільне використання пам'яті, оскільки звіти не будуть затьмарені шумом від активної обробки.

Щоб переконатися, що ви отримали все сміття, вам потрібно виконати дві колекції:

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

Оскільки перша колекція призведе до завершення будь-яких об'єктів із фіналізаторами (але насправді не збирають сміття ці об’єкти). Другий GC буде збирати сміття ці доопрацьовані об’єкти.


Зараз я бачив колекцію з двома проходами в декількох місцях, але, прочитавши уривок документації MSDN для GC.WaitForPendingFinalizers, в якому сказано: "Зачекайте, поки всі фіналізатори завершаться, перш ніж продовжувати. Без цього дзвінка до GC.WaitForPendingFinalizers, цикл робітників нижче може виконуватися одночасно з фіналізаторами. За допомогою цього дзвінка цикл робітника виконується лише після виклику всіх фіналізаторів. " Я просто параноїк на дотик. Чи знаєте ви остаточне джерело для двох проходів?
jerhewet

1
@jerhewet: Ключ до розуміння того, чому потрібні дві колекції, - це розуміння того, що відбувається з об'єктами з фіналізаторами. На жаль, я не маю саме того, про що ви просите, але прочитайте цю статтю та це питання на ТАК .
Пол Руан

10

Ви можете зателефонувати на GC.Collect (), коли вам відомо щось про природу програми, яку не збирає смітник. Заманливо думати, що, як автор, це дуже ймовірно. Однак правда полягає в тому, що GC становить досить добре написану та перевірену експертну систему, і рідко ви знаєте щось про кодові шляхи низького рівня, яких вона не має.

Найкращий приклад, який я можу придумати, де ви можете отримати додаткову інформацію, - це додаток, який цикли між періодами простою та дуже напруженими періодами. Ви хочете отримати найкращі показники роботи для напружених періодів, а тому хочете використати час простою для очищення.

Однак більшу частину часу GC досить розумний, щоб зробити це все одно.


8

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


7

Погляньте на цю статтю Ріко Маріані. Він дає два правила, коли потрібно зателефонувати в GC.Collect (правило 1: "Не"):

Коли зателефонувати в GC.Collect ()


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

5

Один з випадків, коли майже потрібно викликати GC.Collect (), - це автоматизація Microsoft Office через Interop. Об'єкти COM для Office не люблять автоматично випускати, і це може призвести до того, що екземпляри продукту Office займають дуже велику кількість пам'яті. Я не впевнений, це питання чи дизайн. В Інтернеті є багато публікацій на цю тему, тому я не буду надто детально описуватись.

При програмуванні за допомогою Interop кожен окремий об'єкт COM повинен бути звільнений вручну, як правило, за допомогою використання Marshal.ReleseComObject (). Крім того, дзвінок зі збирання сміття вручну може допомогти трохи «очистити». Виклик наступного коду, коли ви закінчите з об'єктами Interop, здається, дуже допомагає:

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

З мого особистого досвіду використання комбінації ReleaseComObject та виклику сміття вручну значно скорочує використання пам'яті продуктів Office, зокрема Excel.


так, я наткнувся на це з .net доступом до excel, який працює і через com об'єкти. Важливо зауважити, що це не буде добре працювати в режимі DEBUG, оскільки операції GC там обмежені. Він буде працювати, як задумано, лише в режимі ЗВ'ЯЗКУ. відповідна посилання: 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), де змінні рівня класу встановлюються на нуль, а смітник збирається.

Краще перестрахуватися, ніж потім шкодувати.


2

я все ще досить впевнений у цьому. Я працюю з 7 років на сервері додатків. Наші великі установки використовують 24 ГБ. Її потужна багатопотокова і ВСІ заклики до GC.Collect () стикалися з дуже жахливими проблемами продуктивності.

Багато компонентів сторонніх розробників використовували GC.Collect (), коли вони вважали, що це розумно робити зараз. Таким чином, проста купа Excel-Reports блокувала App Server для всіх потоків кілька разів на хвилину.

Нам довелося переробляти всі компоненти сторонніх організацій, щоб видалити дзвінки GC.Collect (), і після цього все працювало чудово.

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

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

Мені цікаво одне виняток OOM ... Якби я написав .Net Framework, і я не можу виділити блок пам'яті, я б використав GC.Collect (), дефрагменти пам'яті (??), спробуйте ще раз , і якщо я все-таки не можу знайти вільний блок пам'яті, я б кинув виняток OOM.

Або принаймні зробіть таку поведінку як налаштовану опцію через недоліки проблеми продуктивності з 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 на USB COM-порту, а потім 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.