Почему методы сборки мусора в Java и Python различны?
Python использует метод подсчета ссылок для обработки времени жизни объекта. Таким образом, объект, который больше не используется, будет немедленно уничтожен.
Но в Java GC(сборщик мусора) уничтожает объекты, которые больше не используются в определенное время.
Почему Java выбирает эту стратегию и в чем ее выгода?
Это лучше, чем подход Python?
9 ответов
Есть недостатки использования подсчета ссылок. Одна из наиболее упоминаемых - это циклические ссылки. Предположим, ссылки A на ссылки B, B ссылаются на C и C B. Если A отбросит свою ссылку на B, то и B, и C все равно будут иметь счетчик ссылок 1 и не будут удалены с традиционным подсчетом ссылок. CPython (подсчет ссылок не является частью самого Python, но является частью его реализации на C) ловит циклические ссылки с помощью отдельной процедуры сборки мусора, которую он периодически запускает...
Еще один недостаток: подсчет ссылок может замедлить выполнение. Каждый раз, когда на объект ссылаются и разыменовываются, интерпретатор / ВМ должен проверить, не уменьшилось ли количество до 0 (и затем освободить, если это так). Сборка мусора не требуется для этого.
Кроме того, сборка мусора может быть выполнена в отдельном потоке (хотя это может быть немного сложнее). На машинах с большим количеством ОЗУ и процессах, которые используют память очень медленно, вы можете вообще не захотеть делать GC! Подсчет ссылок был бы небольшим недостатком с точки зрения производительности...
На самом деле подсчет ссылок и стратегии, используемые Sun JVM, представляют собой разные типы алгоритмов сборки мусора.
Существует два основных подхода к отслеживанию мертвых объектов: отслеживание и подсчет ссылок. Трассировка GC начинается с "корней" - таких как ссылки на стек и отслеживает все достижимые (живые) объекты. Все, что не может быть достигнуто, считается мертвым. При подсчете ссылок каждый раз, когда ссылка модифицируется, обновляется счетчик объекта, в котором он задействован. Любой объект, для которого счетчик ссылок установлен в ноль, считается мертвым.
Практически во всех реализациях GC есть компромиссы, но трассировка обычно хороша для операций с высокой пропускной способностью (т.е. быстрой), но имеет более длительное время паузы (большие промежутки, когда пользовательский интерфейс или программа могут зависнуть). Подсчет ссылок может работать меньшими порциями, но в целом будет медленнее. Это может означать меньше зависаний, но в целом производительность ниже.
Кроме того, ГХ для подсчета ссылок требует, чтобы детектор цикла очищал любые объекты в цикле, которые не будут отслеживаться только их счетчиком ссылок. Perl 5 не имел детектора циклов в своей реализации GC и мог пропускать циклическую память.
Также были проведены исследования, чтобы получить лучшее из обоих миров (малое время паузы, высокая пропускная способность): http://cs.anu.edu.au/~Steve.Blackburn/pubs/papers/urc-oopsla-2003.pdf
Даррен Томас дает хороший ответ. Однако одно большое различие между подходами Java и Python состоит в том, что при подсчете ссылок в общем случае (без циклических ссылок) объекты очищаются немедленно, а не в какой-то неопределенной более поздний срок.
Например, я могу написать небрежный, непереносимый код в CPython, такой как
def parse_some_attrs(fname):
return open(fname).read().split("~~~")[2:4]
и дескриптор файла для этого файла, который я открыл, будет немедленно очищен, потому что, как только ссылка на открытый файл исчезнет, файл будет удален, и дескриптор файла будет освобожден. Конечно, если я запускаю Jython или IronPython или, возможно, PyPy, то сборщик мусора не обязательно будет работать намного позже; возможно, сначала у меня закончатся файловые дескрипторы, и моя программа потерпит крах.
Так что вы ДОЛЖНЫ писать код, который выглядит как
def parse_some_attrs(fname):
with open(fname) as f:
return f.read().split("~~~")[2:4]
но иногда людям нравится полагаться на подсчет ссылок, чтобы всегда освобождать свои ресурсы, потому что это может иногда сделать ваш код немного короче.
Я бы сказал, что лучшим сборщиком мусора является тот, который обладает наилучшей производительностью, который в настоящее время выглядит как сборочные сборщики мусора в стиле Java, которые могут работать в отдельном потоке и имеют все эти сумасшедшие оптимизации и т. Д. Напишите ваш код должен быть незначительным и в идеале не существует.
Я думаю, что статья IBM " Теория и практика Java: краткая история сборки мусора" должна помочь объяснить некоторые вопросы, которые у вас есть.
Один большой недостаток отслеживания GC в Java заключается в том, что время от времени он "останавливает мир" и останавливает приложение на относительно долгое время, чтобы выполнить полный GC. Если куча большая, а дерево объектов сложное, она замерзнет на несколько секунд. Также каждый полный сборщик мусора снова и снова посещает все дерево объектов, что, вероятно, весьма неэффективно. Другой недостаток Java в GC заключается в том, что вы должны указать jvm, какой размер кучи вы хотите (если значение по умолчанию недостаточно хорошее); JVM извлекает из этого значения несколько пороговых значений, которые будут запускать процесс GC, когда в куче слишком много мусора.
Я полагаю, что это на самом деле является основной причиной резкого ощущения Android (на основе Java) даже на самых дорогих мобильных телефонах по сравнению с плавностью iOS (на основе ObjectiveC и с использованием RC).
Я хотел бы видеть опцию jvm, чтобы включить управление памятью RC, и, возможно, оставить GC работать только в крайнем случае, когда не осталось больше памяти.
Сборка мусора происходит быстрее (более эффективно), чем подсчет ссылок, если у вас достаточно памяти. Например, копирующий gc пересекает "живые" объекты и копирует их в новое пространство и может вернуть все "мертвые" объекты за один шаг, пометив всю область памяти. Это очень эффективно, если у вас достаточно памяти. Коллекции поколений используют знания о том, что "большинство предметов умирают молодыми"; часто только несколько процентов объектов должны быть скопированы.
[Это также причина, почему gc может быть быстрее, чем malloc / free]
Подсчет ссылок занимает гораздо больше места, чем сборщик мусора, поскольку он восстанавливает память в тот самый момент, когда становится недоступным. Это хорошо, когда вы хотите прикрепить финализаторы к объектам (например, закрыть файл, как только объект File станет недоступным). Система подсчета ссылок может работать, даже когда только несколько процентов памяти свободны. Но затраты на управление, связанные с увеличением и уменьшением счетчиков при каждом назначении указателя, требуют значительных затрат времени, и для восстановления циклов все еще требуется некоторая сборка мусора.
Таким образом, компромисс очевиден: если вам нужно работать в условиях ограниченного объема памяти или если вам нужны точные финализаторы, используйте подсчет ссылок. Если у вас достаточно памяти и скорость, используйте сборщик мусора.
Подсчет ссылок особенно сложно сделать эффективно в многопоточной среде. Я не знаю, как вы могли бы начать делать это, не вдаваясь в аппаратные транзакции или подобные (в настоящее время) необычные атомарные инструкции.
Подсчет ссылок легко осуществить. JVM потратили много денег на конкурирующие реализации, поэтому не удивительно, что они реализуют очень хорошие решения для очень сложных проблем. Однако на JVM становится все проще ориентироваться на ваш любимый язык.
Последние Sun Java VM на самом деле имеют несколько алгоритмов GC, которые вы можете настроить. Спецификации Java VM преднамеренно опущены, определяя фактическое поведение GC, чтобы разрешить разные (и множественные) алгоритмы GC для разных VM.
Например, для всех людей, которым не нравится подход "остановка мира" поведения Sun Java VM GC по умолчанию, есть виртуальная машина, такая как IBM WebSphere Real Time, которая позволяет приложениям реального времени работать на Java.
Поскольку спецификация Java VM общедоступна, (теоретически) ничто не мешает реализовать Java VM, использующую алгоритм CPython GC.
Позднее в игре, но я думаю, что одно существенное обоснование RC в python - это его простота. Посмотрите это письмо Алекса Мартелли, например.
(Я не смог найти ссылку за пределами кэша Google, дату электронного письма от 13 октября 2005 года в списке Python).