Як працює збір сміття Java з циркулярними посиланнями?


161

З мого розуміння, збирання сміття на Java очищує деякі об'єкти, якщо більше нічого не вказує на цей об’єкт.

Моє запитання: що станеться, якщо у нас є щось подібне:

class Node {
    public object value;
    public Node next;
    public Node(object o, Node n) { value = 0; next = n;}
}

//...some code
{
    Node a = new Node("a", null), 
         b = new Node("b", a), 
         c = new Node("c", b);
    a.next = c;
} //end of scope
//...other code

a, bі cповинні бути зібрані сміття, але всі вони посилаються на інші об'єкти.

Як з цим вирішується збір сміття Java? (чи це просто злив пам'яті?)


1
Дивіться: stackoverflow.com/questions/407855/… , конкретно другу відповідь від @gnud.
Сет

Відповіді:


161

GC Java вважає об'єкти "сміттям", якщо вони недоступні через ланцюг, що починається з кореня збору сміття, тому ці об'єкти будуть зібрані. Хоча об’єкти можуть вказувати один на одного, щоб утворювати цикл, вони все одно сміття, якщо їх відрізають від кореня.

Дивіться розділ про недоступні об’єкти в Додатку A: Правда про збирання сміття в Java-платформі. Продуктивність: Стратегії та тактика для деталей горіхів.


14
У вас є посилання на це? Це важко перевірити.
tangens

5
Я додав посилання. Ви також можете замінити метод finalize () об'єкта, щоб дізнатись, коли він збирається (хоча це про єдине, що я рекомендував би використовувати finalize () для).
Білл Ящірка

1
Просто для уточнення останнього коментаря ... покладіть на друк заяву про налагодження в метод завершення, який виводить унікальний ідентифікатор для об'єкта. Ви зможете побачити всі об'єкти, на які посилаються один на одного, зібрані.
Білл Ящірка

4
"... досить розумний, щоб розпізнати ..." звучить заплутано. GC не повинні розпізнавати цикли - вони просто недоступні, отже, сміття
Олександр Малахов

86
@tangens "У вас є посилання на це?" в дискусії про вивезення сміття. Найкраще. Каламбур. Колись.
Michał Kosmulski

139

так, збирач сміття Java обробляє циркулярні посилання!

How?

Існують спеціальні об’єкти, які називаються корінням для збирання сміття (GC коріння). Вони завжди доступні, як і будь-який об'єкт, який має їх у корені.

Проста програма Java має такі корені GC:

  1. Локальні змінні в основному методі
  2. Основна нитка
  3. Статичні змінні основного класу

введіть тут опис зображення

Щоб визначити, які об'єкти більше не використовуються, JVM з періодичністю виконує те, що дуже влучно називається алгоритмом розмітки та ревізії . Він працює наступним чином

  1. Алгоритм обходить усі посилання на об'єкти, починаючи з коренів GC, і позначає кожен знайдений об'єкт як живий.
  2. Повернута вся пам'ять купи, яка не зайнята позначеними об'єктами. Він просто позначений як вільний, по суті витіснений від невикористаних предметів.

Тож якщо будь-який об’єкт недоступний від коренів GC (навіть якщо він є самопосиланням або циклічним посиланням), він буде підданий збору сміття.

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

введіть тут опис зображення

Джерело: Управління пам'яттю Java


3
Ідеальне пояснення! Дякую! :)
Йован Перович

Дякуємо, що зв’язали цю книгу. Повна чудова інформація про цю та інші теми розвитку Java!
Дрой

14
На останньому малюнку є недоступний об'єкт, але його знаходиться в розділі об'єктів, що доступні.
La VloZ Merrill

13

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

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

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

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


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

@ Йорг W Міттаг: Безумовно, це правда, хоча я не знаю (досить актуальний) JVM, який використовує підрахунок посилань, тому здається, що (принаймні для мене) це має велике значення для початкового питання.
Джеррі Труну

@ Jörg W Mittag: Принаймні, я вважаю, що Jikes RVM в даний час використовує колектор Immix, який є колектором трасування на основі регіону (хоча він також використовує підрахунок посилань). Я не впевнений, ви посилаєтесь на цей підрахунок посилань чи інший колектор, який використовує підрахунок посилань без простеження (я б здогадався про останнє, оскільки я ніколи не чув, щоб Immix називав "переробник").
Джеррі Труну

Я трохи змішався: Recycler (був?) Реалізований у Джалапено, алгоритм, про який я думав, який (був?) Реалізований у Jikes - це Ultimate Reference Counting . Атлхоуф, звичайно, сказати, що Джикс використовує той чи інший сміттєзбірник, цілком безрезультатно, враховуючи, що Джікс та особливо MMtk спеціально розроблені для швидкого розвитку та тестування різних сміттєзбірників у межах одного JVM.
Йорг W Міттаг

2
Ultimate Reference Counting був розроблений в 2003 році тими ж людьми, які проектували Immix в 2007 році, тому я здогадуюсь, що останній, ймовірно, витіснив перший. URC був спеціально розроблений таким чином, щоб його можна було поєднувати з іншими стратегіями, і насправді в документі URC прямо зазначається, що URC є лише кроком до колектора, який поєднує в собі переваги трасування та підрахунку еталонів. Я здогадуюсь, що Іммікс - це колекціонер. У всякому разі, Recycler - це чистий збірник підрахунку еталонів, який, тим не менше, може виявляти та збирати цикли: WWW.Research.IBM.Com/people/d/dfb/recycler.html
Jörg W

13

Ви праві. Конкретна форма збору сміття, яку ви описуєте, називається " підрахунком посилань ". Те, як це працює (концептуально, принаймні, більшість сучасних реалізацій опорного рахунку насправді реалізується зовсім по-іншому) у найпростішому випадку виглядає так:

  • щоразу, коли додається посилання на об'єкт (наприклад, він присвоюється змінній або полі, передається методу тощо), його посилання збільшується на 1
  • кожного разу, коли посилання на об’єкт видаляється (метод повертається, змінна виходить із сфери застосування, поле перепризначається іншому об'єкту або об'єкт, який містить поле, отримує сам сміття), кількість посилань зменшується на 1
  • як тільки число посилається на 0, більше немає посилання на об'єкт, що означає, що ніхто більше не може його використовувати, тому це сміття і його можна збирати

І ця проста стратегія має саме таку проблему, яку ви вирішите: якщо посилання А і В посилання А, то обидва їх посилання не можуть бути меншими за 1, а це означає, що вони ніколи не будуть зібрані.

Існує чотири способи вирішення цієї проблеми:

  1. Ігноруйте це. Якщо у вас достатньо пам’яті, ваші цикли невеликі і нечасті, а час виконання короткий, можливо, ви можете піти, просто не збираючи цикли. Подумайте про інтерпретатора скриптів оболонки: сценарії оболонок зазвичай працюють лише кілька секунд і не виділяють багато пам’яті.
  2. Поєднайте свій контрольний підрахунок сміттєзбірника з іншим сміттєзбірником, який не має проблем з циклами. CPython робить це, наприклад: головний збирач сміття в CPython є опорним підрахунковим колектором, але час від часу за допомогою циклу збирання сміття запускається сміттєзбірник.
  3. Визначте цикли. На жаль, виявлення циклів у графіку є досить дорогою операцією. Зокрема, для цього потрібні такі ж накладні витрати, як і колектор трасування, тож ви можете так само добре використовувати один із них.
  4. Не реалізовуйте алгоритм наївно, як ви, і я: з 1970-х років було розроблено кілька досить цікавих алгоритмів, які поєднують виявлення циклу та підрахунок посилань в одній операції розумним способом, що значно дешевше, ніж будь-яке їх виконання як окремо, так і колекціонування кальки.

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

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


1
Оскільки питання стосується Java, я думаю, що варто згадати, що Java не використовує підрахунок посилань і, отже, проблема не існує. Також посилання на wikipedia буде корисним як "подальше читання". Інакше чудовий огляд!
Олександр Малахов

Я щойно прочитав ваші коментарі до повідомлення Джеррі Коффіна, тому зараз я не впевнений у цьому :)
Олександр Малахов

8

Java GC насправді не ведуть себе так, як ви описуєте. Точніше сказати, що вони починаються з базового набору об'єктів, який часто називають "коренями GC", і збиратимуть будь-який об'єкт, до якого неможливо дістатись із кореня.
Коріння GC включають такі речі, як:

  • статичні змінні
  • локальні змінні (включаючи всі застосовні посилання "цього"), що знаходяться зараз у стеці запущеної нитки

Отже, у вашому випадку, коли локальні змінні a, b і c виходять із сфери застосування в кінці вашого методу, більше немає коренів GC, які містять, прямо чи опосередковано, посилання на будь-який із трьох ваших вузлів, і вони матимуть право на вивезення сміття.

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


"... наразі знаходиться в стеці запущеної нитки ..." чи не сканує стеки всіх потоків, щоб не пошкодити дані інших потоків?
Олександр Малахов

6

Ця стаття (більше не доступна) йде в глибину про збирач сміття (концептуально ... є кілька реалізацій). Відповідна частина вашого допису - "A.3.4 недоступна":

A.3.4 недоступність Об'єкт переходить у недоступний стан, коли немає більш сильних посилань на нього. Коли об’єкт недоступний, він є кандидатом на колекцію. Зверніть увагу на формулювання: Тільки тому, що об’єкт є кандидатом на колекцію, це не означає, що він буде негайно зібраний. JVM може затягувати збір, поки не з’явиться негайна потреба в пам'яті, яку споживає об'єкт.



1
посилання більше не доступні
titus

1

Збір сміття зазвичай не означає "очистити якийсь об'єкт, якщо більше нічого не вказує на цей об'єкт" (це підрахунок посилань). Збір сміття приблизно означає пошук об’єктів, до яких неможливо дістатися з програми.

Тож у вашому прикладі, після виходу з області a, b і c, вони можуть бути зібрані GC, оскільки ви більше не можете отримати доступ до цих об'єктів.


"Збір сміття приблизно означає пошук об'єктів, до яких неможливо дістатися з програми". У більшості алгоритмів ГК це насправді навпаки. Ви починаєте з коренів GC і бачите, що ви можете знайти, решта вважається сміттям, яке не стосується.
Фредрік

1
Підрахунок довідок є однією з двох основних стратегій впровадження збору сміття. (Інше простежується.)
Йорг W Міттаг

3
@ Йорг: Більшість випадків сьогодні, коли люди говорять про сміттєзбірники, вони мають на увазі колекціонерів на основі якогось алгоритму відмітки. Підрахунок посилань, як правило, те, що ви застрягли, якщо у вас немає сміттєзбірника. Це правда, що підрахунок посилань є в певному сенсі стратегією збору сміття, але навряд чи існує сьогодні gc, яка побудована на ній, тому мовляв, це стратегія gc просто збирається заплутати людей, оскільки на практиці це вже не gc стратегія, але альтернативний спосіб управління пам'яттю.
Фредрік

1

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.