Ви дійсно не можете робити конспекти щодо відповідного способу використання всіх програм GC. Вони різко змінюються. Тож я поговорю з .NET, про який ви спочатку згадували.
Ви повинні знати поведінку GC досить інтимно, щоб зробити це з будь-якої логіки чи причини.
Єдина порада щодо колекції, яку я можу дати, - це ніколи не робити.
Якщо ви справді знаєте складні деталі GC, вам не знадобиться моя порада, тому це не має значення. Якщо ви ще не знаєте зі 100% впевненістю, це допоможе, і вам доведеться шукати в Інтернеті і знайти відповідь, як це: Вам не слід телефонувати в GC.Collect , або, як альтернатива: Вам слід дізнатися деталі того, як працює GC. всередині і зовні, і тільки тоді ви дізнаєтесь відповідь .
Є одне безпечне місце, яке має сенс використовувати GC.Collect :
GC.Collect - це доступний API, який ви можете використовувати для профілювання речей часу. Ви можете профайлювати один алгоритм, збирати та профілювати інший алгоритм одразу після того, щоб дізнатися, що GC першого альго не відбулося під час другого сканування результатів.
Цей вид профілювання - це єдиний раз, коли я б хотів запропонувати комусь вручну збирати.
Надуманий приклад все одно
Один з можливих випадків використання - якщо ви завантажуєте дійсно великі речі, вони опиняться у великій купі об’єктів, яка прямуватиме до Gen 2, хоча знову ж таки Gen 2 - це довгоживучі об'єкти, оскільки вона збирається рідше. Якщо ви знаєте, що ви завантажуєте короткотривалі об’єкти в Gen 2 з будь-якої причини, ви можете їх швидше очистити, щоб ваш Gen 2 був меншим, а колекції - швидшими.
Це найкращий приклад, який я міг би придумати, і це не добре - тиск LOH, який ти тут будуєш, спричинить частіші колекції, а колекції такі часті, як це є - швидше за все, це було б очищенням LOH так само, як швидко, як ви видували це тимчасовими предметами. Я просто не довіряю собі вважати кращу частоту збору, ніж сама ГК - налаштована людьми, набагато розумнішими за мене .
Тож давайте поговоримо про деякі семантики та механізми в .NET GC ... або ..
Все, що я думаю, я знаю про .NET GC
Будь ласка, будь-хто, хто знайде тут помилки - виправте мене. Значна частина GC добре відома як чорна магія, і хоча я намагався залишити деталі, в яких я був невпевнений, я, мабуть, все-таки помилився.
Нижче цілеспрямовано пропущені численні деталі, про які я не впевнений, а також набагато більшу кількість інформації, про яку я просто не знаю. Використовуйте цю інформацію на свій страх і ризик.
Концепції GC
.NET GC виникає в непослідовні часи, тому його називають "недетермінованим", це означає, що ви не можете розраховувати на те, що він відбуватиметься в конкретний час. Це також генераційний сміттєзбірник, а це означає, що він розподіляє ваші об’єкти на кількість пропусків GC, які вони пережили.
Об'єкти в Gen 0 купи пережили 0 колекцій, вони були нещодавно зроблені, так що нещодавно з моменту їх створення не відбулося жодної колекції. Об'єкти у вашій грі Gen 1 прожили один прохід колекції, а також об’єкти у вашій грі Gen 2 прожили два проходи колекції.
Тепер варто зазначити причину, згідно з якою вони кваліфікують ці конкретні покоління та розділи відповідно. .NET GC розпізнає лише ці три покоління, тому що колекція передач, що переходять на ці три купи, дещо відрізняється. Деякі об'єкти можуть вижити, колекція проходить тисячі разів. GC просто залишає їх на іншій стороні купи гена 2, немає сенсу розділяти їх більше ніде, оскільки вони насправді є Gen 44; колекційний пропуск на них такий самий, як у всіх у ген 2 купи.
Ці конкретні покоління мають смислові цілі, а також реалізовані механізми, які їх шанують, і я до кінця дістанусь до них.
Що в колекції
Основна концепція пропуску колекції GC полягає в тому, що він перевіряє кожен об'єкт у купі простору, щоб побачити, чи є ще живі посилання (корені GC) на ці об'єкти. Якщо для об'єкта знайдено корінь GC, це означає, що в даний час виконаний код все ще може охопити та використовувати цей об’єкт, тому його неможливо видалити. Однак якщо корінь GC не знайдено для об'єкта, це означає, що запущений процес більше не потребує об'єкта, тому він може видалити його, щоб звільнити пам'ять для нових об’єктів.
Тепер після того, як буде закінчено прибирати купу предметів і залишити їх в спокої, з’явиться прикрий побічний ефект: вільний пробіл у просторі між живими об’єктами, куди було видалено мертвих. Ця фрагментація пам'яті, якщо її залишити в спокої, просто втратить пам'ять, тому колекції зазвичай роблять те, що називається «ущільненням», де вони беруть усі живі об’єкти, що залишилися, і стискають їх у купу, щоб вільна пам’ять була суміжною з одного боку купи для Gen 0.
Тепер, маючи уявлення про 3 купи пам'яті, всі розділені на кількість пропусків колекції, які вони пережили, давайте поговоримо про те, чому існують ці розділи.
Колекція Gen 0
Gen 0, будучи абсолютними новітніми об'єктами, має тенденцію бути дуже маленьким - тому ви можете сміливо збирати його дуже часто . Частота гарантує, що купа залишається невеликою, а колекції - дуже швидкими, оскільки вони збираються над такою невеликою групою. Це грунтується більш-менш на евристиці, яка стверджує: Велика більшість тимчасових об'єктів, які ви створюєте, є дуже тимчасовими, тому тимчасові вони більше не будуть використовуватися або посилатися майже відразу після використання, і таким чином їх можна збирати.
Колекція Gen 1
Будь 1, будучи об'єктами, які не потрапили до цієї дуже тимчасової категорії об'єктів, все ще може бути досить короткочасним, оскільки все-таки значна частина створених об'єктів довго не використовується. Тому Gen 1 також збирає досить часто, знову зберігаючи невелику купу, щоб колекції були швидкими. Однак припущення менше, тому що об'єкти тимчасовіші ніж Gen 0, тому вони збираються рідше, ніж Gen 0
Я скажу, що відверто не знаю технічних механізмів, які відрізняються між пропускним кодом Gen 0 і Gen 1, якщо вони є, крім частоти, яку вони збирають.
Колекція Gen 2
Gen 2 тепер повинен бути матір'ю всіх купи, правда? Ну так, це більш-менш правильно. Тут живуть усі ваші постійні об’єкти - об’єкт, наприклад, ваше Main()
життя, і все, на що Main()
посилається, тому що вони будуть вкорінені до тих пір, поки ви не Main()
повернетеся в кінці процесу.
Зважаючи на те, що Gen 2 - це відро для всього, що не могли зібрати інші покоління, об'єкти в основному є постійними або принаймні довго живуть. Тож визнання дуже мало того, що в Gen 2, насправді буде чимось, що можна збирати, не потрібно збирати часто. Це дозволяє його збір також проходити повільніше, оскільки він виконується набагато рідше. Таким чином, це в основному там, де вони застосували всі додаткові способи поведінки для непарних сценаріїв, оскільки у них є час на їх виконання.
Велика купа об’єктів
Одним із прикладів додаткової поведінки в ген 2 є те, що він також збирає колекцію на великій купі об'єктів. До цього часу я повністю говорив про малу купі об’єктів, але час виконання .NET виділяє речі певного розміру окремій купі через те, що я назвав вище ущільненням. Для ущільнення потрібні переміщення об’єктів, коли колекції закінчуються на Малій купі об’єктів. Якщо в Gen 1 є живий об'єкт 10 Мб, це займе набагато більше часу, щоб завершити ущільнення після його збору, тим самим уповільнивши колекцію Gen 1. Таким чином, об'єкт 10 Мб виділяється на Велику групу об'єктів і збирається під час ген 2, який працює так рідко.
Фіналізація
Інший приклад - об’єкти з фіналізаторами. Ви ставите фіналізатор на об'єкт, який посилається на ресурси, що виходять за межі .NETs GC (некеровані ресурси). Фіналізатор - це єдиний спосіб, коли GC вимагає збирати некерований ресурс - ви реалізуєте свій фіналізатор, щоб зробити ручне збирання / видалення / випуск некерованого ресурсу, щоб переконатися, що він не витікає з вашого процесу. Коли GC приступить до виконання ваших об'єктів фіналізатора, тоді ваша реалізація очистить некерований ресурс, зробивши GC здатним видалити ваш об’єкт, не ризикуючи витікати ресурс.
Механізм, за допомогою якого фіналізатори це роблять, - це посилання безпосередньо в чергу завершення. Коли час виконання виділяє об'єкт з фіналізатором, він додає вказівник на цей об’єкт до черги фіналізації та блокує ваш об'єкт на місці (називається закріпленням), щоб ущільнення не перемістило його, що порушило б посилання черги на завершення. По мірі проходження колекції, зрештою, буде виявлено, що ваш об'єкт більше не має кореня GC, але доопрацювання має бути виконано до того, як його можна буде зібрати. Отже, коли об’єкт мертвий, колекція перемістить його посилання з черги на завершення та розмістить посилання на нього, що відомо як черга "FReachable". Потім колекція продовжується. В інший "недетермінований" час у майбутньому, окремий потік, відомий як потік Finalizer, буде проходити через чергу FReachable, виконуючи фіналізатори для кожного з об'єктів, на які посилається. Після її завершення черга з можливістю очищення порожня, і вона трохи перегорнула заголовок кожного об'єкта, що говорить, що вони не потребують доопрацювання (цей біт також можна перевернути вручнуGC.SuppressFinalize
що є загальним у Dispose()
методах), я також підозрюю, що він відкрутив об'єкти, але не цитуйте мене з цього приводу. Наступна колекція, яка з’явиться на будь-якій купі цього об’єкта, нарешті збирає її. Колекції Gen 0 навіть не звертають уваги на об’єкти, у яких потрібен біт для доопрацювання, він автоматично просуває їх, навіть не перевіряючи їх корінь. Укорінений об'єкт, який потребує доопрацювання в Gen 1, буде кинутий у FReachable
чергу, але колекція нічого іншого не робить, тому він переходить у Gen 2. Таким чином, усі об'єкти, які мають фіналізатор, не роблять GC.SuppressFinalize
буде зібрано у 2-му поколінні.