Как работает assembly мусора Java с циркулярными ссылками?

По моему мнению, garbage collection в 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 должны быть собраны мусором, но на них все ссылаются другими объектами.

Как это делает assembly мусора Java? (или это просто утечка памяти?)

GC Java рассматривает объекты «мусор», если они недоступны через цепочку, начинающуюся с корня сборщика мусора, поэтому эти объекты будут собраны. Даже если объекты могут указывать друг на друга, чтобы сформировать цикл, они все еще мусор, если они отрезаны от корня.

См. Раздел о недоступных объектах в Приложении A: «Правда об сборке мусора в производительности платформы Java: страtagsи и тактика» .

yes Java Сборщик мусора обрабатывает циркулярную ссылку!

 How? 

Существуют специальные объекты, называемые корнями для сбора мусора (корни GC). Они всегда достижимы, а также любой объект, который имеет их в своем собственном корне.

Простой Java-приложение имеет следующие корни GC:

  1. Локальные переменные в основном методе
  2. Основная тема
  3. Статические переменные основного classа

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

Чтобы определить, какие объекты больше не используются, JVM периодически запускает то, что очень точно называют алгоритмом метки и развертки . Он работает следующим образом

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

Поэтому, если какой-либо объект недоступен из корней GC (даже если он является самореферентным или циклическим), он будет подвергнут сбору мусора.

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

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

Источник: Управление памятью Java

Сборщик мусора начинается с некоторого «корневого» набора мест, которые всегда считаются «доступными», таких как регистры процессора, стек и глобальные переменные. Он работает, находя любые указатели в этих областях и рекурсивно обнаруживая все, на что они указывают. Как только все это обнаружено, все остальное – мусор.

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

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

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

Ты прав. Конкретная форма сбора мусора, которую вы описываете, называется « подсчет ссылок ». То, как это работает (концептуально, по крайней мере, большинство современных реализаций подсчета ссылок фактически реализовано совсем по-другому) в простейшем случае выглядит следующим образом:

  • всякий раз, когда добавляется ссылка на объект (например, она назначается переменной или полем, передается методу и т. д.), счетчик ссылок увеличивается на 1
  • всякий раз, когда ссылка на объект удаляется (метод возвращается, переменная выходит за пределы области, поле переназначается другому объекту или объект, который содержит поле, получает сам мусор, собранный), счетчик ссылок уменьшается на 1
  • как только число ссылок достигает 0, больше нет ссылки на объект, что означает, что никто больше не может его использовать, поэтому он является мусором и может быть собран

И эта простая страtagsя имеет именно ту проблему, которую вы описываете: если A ссылается на B и B, ссылается на A, то оба их подсчета ссылок никогда не могут быть меньше 1, а это значит, что они никогда не будут собраны.

Существует четыре способа решения этой проблемы:

  1. Игнорируй это. Если у вас достаточно памяти, ваши циклы малы и нечасты, а время выполнения короткое, возможно, вам удастся просто не собирать циклы. Подумайте о интерпретаторе сценариев оболочки: сценарии оболочки обычно работают всего несколько секунд и не выделяют много памяти.
  2. Объедините свой счетный сборщик мусора с другим сборщиком мусора, который не имеет проблем с циклами. CPython делает это, например: основной сборщик мусора в CPython является сборщиком подсчета ссылок, но время от времени трассировочный сборщик мусора запускается для сбора циклов.
  3. Определите циклы. К сожалению, обнаружение циклов на графике является довольно дорогостоящей операцией. В частности, это требует почти столько же накладных расходов, что и сборщик трассировки, поэтому вы могли бы использовать один из них.
  4. Не реализуйте алгоритм наивным образом, как вы и я: с 1970-х годов было разработано несколько довольно интересных алгоритмов, которые объединяют обнаружение цикла и подсчет ссылок в одной операции умным способом, который значительно дешевле, чем их выполнение как отдельно, так и для сбора трассировочного коллектора.

Кстати, другой основной способ реализовать сборщик мусора (и я уже намекнул на это пару раз выше) отслеживает . Трассировочный compilation основан на концепции достижимости . Вы начинаете с некоторого набора корней, который, как вы знаете, всегда доступен (глобальные константы, например, или class Object , текущая лексическая область, текущий стек стека), и оттуда вы отслеживаете все объекты, доступные из набора root, затем все объекты, которые достижимы из объектов, доступных из набора корней и т. д., пока у вас не будет транзитивного закрытия. Все, что не в этом закрытии, – это мусор.

Поскольку цикл доступен только внутри себя, но недоступен из набора корней, он будет собран.

Java GCs фактически не ведут себя так, как вы описываете. Точнее сказать, что они начинаются с базового набора объектов, часто называемых «GC-корнями», и собирают любой объект, который не может быть достигнут из корня.
Корни GC include такие вещи, как:

  • статические переменные
  • локальные переменные (включая все применимые «эти» ссылки), которые в настоящее время находятся в стеке работающего streamа

Итак, в вашем случае, когда локальные переменные a, b и c выходят за пределы области действия в конце вашего метода, больше нет корней GC, которые прямо или косвенно содержат ссылку на любой из ваших трех узлов и они будут иметь право на garbage collection.

Ссылка TofuBeer имеет более подробную информацию, если вы этого хотите.

Эта статья (больше не доступна) углубляется в сборщик мусора (концептуально … существует несколько реализаций). Соответствующая часть вашего сообщения – «A.3.4 Недостижимая»:

A.3.4 Недостижимый Объект переходит в недостижимое состояние, когда нет более сильных ссылок на него. Когда объект недоступен, он является кандидатом на сбор. Обратите внимание на формулировку: только потому, что объект является кандидатом на сбор, это не означает, что он будет немедленно собран. JVM может задерживать сбор до тех пор, пока не будет немедленно потребована память, потребляемая объектом.

Сбор мусора обычно не означает «очистить какой-либо объект, если ничто иное не указывает на этот объект» (это подсчет ссылок). Сбор мусора примерно означает поиск объектов, которые не могут быть достигнуты из программы.

Таким образом, в вашем примере после того, как a, b и c исчезнут из области видимости, они могут быть собраны GC, так как вы больше не можете обращаться к этим объектам.

Билл ответил на ваш вопрос напрямую. Как сказал Амнон, ваше определение сбора мусора – это просто подсчет ссылок. Я просто хотел добавить, что даже очень простые алгоритмы, такие как assembly меток и подметания и копирования, легко обрабатывают циклические ссылки. Итак, ничего волшебного!

  • Лучшая практика аутентификации на основе токенов REST с JAX-RS и Джерси
  • Проблемы с методом Paint в Java, смехотворная скорость обновления
  • Как итеративно генерировать k элементов подмножеств из набора размера n в java?
  • Циркулярные ссылки в Java
  • Идиома Java для lambda с интерфейсами, отличными от SAM
  • JavaFX FileChooser Throws Error (возможно, легко исправить, но все еще запутано)
  • Не удалось прочитать InputStream из Java Process (Runtime.getRuntime (). Exec () или ProcessBuilder)
  • Как получить первый день номера данной недели в Java
  • Невозможно создать прозрачный и незадекларированный JFrame в JDK7 при включении nimbus
  • Самый простой способ шифрования текстового файла в java
  • Как удалить объекты из массива в Java?
  • Давайте будем гением компьютера.