Прокачайся в код-рев’ю: для перших 50 учасників — курс безкоштовний

час читання: 3 хв

Збирання сміття в .NET Framework: як оптимізувати пам’ять у додатках

У статті розглядається збирання сміття в .NET Framework і пояснюється, як відбувається процес, що таке коріння додатка, і в чому різниця між Finalization queue та F-reachable queue.

Автор статті — Lead Software Engineer EPAM Продіпто Банерджі.

Що таке збирання сміття в .NET Framework

Збирання сміття належить до автоматичного процесу керування пам’яттю, який пов’язаний із виділенням та звільненням пам’яті для об’єктів, створених додатками. Коли додаткам потрібна пам’ять, збирач сміття (англ. garbage collector, GC) виділяє її для об’єкта з керованої купи.

У .NET Framework є середовище виконання — загальномовне виконуюче середовище (англ. common language runtime, CLR). Воно виконує код і надає сервіси для полегшення розробки. У середовищі CLR збирач сміття відповідає за процес автоматичного керування пам’яттю.

Приклад 1

Це зображення наочно демонструє, що в керованій купі наразі зберігаються 2 об’єкти (об’єкт A та об’єкт B), і NextObjPtr вказує на наступне доступне місце для зберігання об’єктів у купі.

Що таке збирання сміття в Java і як воно працює?

Коріння додатка

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

Коріння додатка включають в себе всі глобальні та статичні покажчики об’єктів у додатку. Крім того, частиною коріння додатка також вважаються будь-які локальні змінні або покажчики об’єктів параметрів, що знаходяться в стеку потоку. Навіть регістри процесора, що містять покажчики на об’єкти в купі, входять до коріння додатка. Середовище CLR зберігає і контролює список активного коріння, доступного для алгоритму збирача сміття.

Приклад 2

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

Як тільки керована купа заповнюється, запускається збирач сміття для звільнення пам’яті. У процесі виконання він спочатку розглядає всі об’єкти в купі як сміття, а потім проходить корінням і будує граф, що включає всі об’єкти, доступні з цих корінь. Цей процес повторюється для всіх наступних корінь, поки граф не буде сформовано повністю.

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

Приклад 3

Завдання збирача — відокремити об’єкти, що є сміттям, від об’єктів, які до сміття не належать. Збирач переміщує об’єкти, які не є сміттям, у нове місце, стискаючи простір, що використовується, і підвищуючи ефективність його використання. Відповідно, збирач також оновлює коріння додатка, гарантуючи, що покажчики тепер посилаються на нові місця розташування об’єктів.

Черга фіналізації (Finalization queue) та черга завершення (F-reachable queue)

Коли додаток створює новий об’єкт, збирач сміття виділяє пам’ять із купи. Якщо тип об’єкта включає finalize метод, то посилання на об’єкт поміщається у Finalization queue — чергу фіналізації. Ця черга являє собою внутрішню структуру даних, контрольовану збирачем сміття. Кожен запис у черзі вказує на об’єкт, для якого необхідно викликати його finalize метод, перш ніж пам’ять, виділена для цього об’єкта, може бути звільнена.

Приклад 4

Припустимо, збирач сміття визначив об’єкти G, D та C як сміття. У процесі збирання він сканує чергу фіналізації для пошуку покажчиків на ці об’єкти. У разі виявлення покажчика він видаляє об’єкт із черги фіналізації та переміщує його в F-reachable queue — чергу завершення.

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

Приклад 5

Кожен запис у черзі завершення відповідає об’єкту, finalize метод якого може бути викликаний. Таким чином, пам’ять об'єктів B та H, у яких немає finalize методу, буде звільнено. Однак пам’ять об’єкта G не буде звільнено, оскільки його finalize метод ще не викликаний.

Виконання finalize методів відбувається в окремому потоці. Коли черга завершення порожня, цей потік переходить у режим очікування. Однак коли в черзі завершення з’являється запис, потік приступає до виконання finalize методу об’єкта. Після цього об’єкт видаляється з черги завершення. Через таку поведінку рекомендується не виконувати жодного коду всередині finalize методу, щоб уникнути потенційних проблем.

Приклад 6

Висновок

Загальномовне виконуюче середовище ініціює збирання сміття — автоматичний процес, який використовує вбудовані черги для виконання finalize методу, пов’язаного з об’єктами. Для розробників важливо унеможливити виконання коду в finalize методі, щоб уникнути можливих проблем.

Крім цього, рекомендується видаляти екземпляр об’єкта відразу після того, як він виконав поставлене завдання або завершив роботу. Дотримуючись такого підходу, керована купа не буде заповнена «забутими» об’єктами.