Почему методы сбора мусора в Java и Python различаются?

StackOverflow https://stackoverflow.com/questions/21934

  •  09-06-2019
  •  | 
  •  

Вопрос

Python использует метод подсчета ссылок для обработки времени жизни объекта.Таким образом, объект, который больше не используется, будет немедленно уничтожен.

Но в Java GC (сборщик мусора) уничтожает объекты, которые больше не используются в определенное время.

Почему Java выбирает эту стратегию и какая от этого польза?

Лучше ли это, чем подход Python?

Это было полезно?

Решение

Существуют и недостатки использования подсчета ссылок.Одна из наиболее упоминаемых — циклические ссылки:Предположим, что A ссылается на B, B ссылается на C и C ссылается на B.Если бы A удалил свою ссылку на B, счетчик ссылок B и C по-прежнему будет равен 1, и они не будут удалены при традиционном подсчете ссылок.CPython (подсчет ссылок не является частью самого Python, а является частью его реализации на C) перехватывает циклические ссылки с помощью отдельной процедуры сбора мусора, которую он периодически запускает...

Еще один недостаток:Подсчет ссылок может замедлить выполнение.Каждый раз, когда на объект ссылаются и разыменовывают его, интерпретатор/VM должен проверить, снизился ли счетчик до 0 (а затем освободить его, если это произошло).Сборщик мусора не должен этого делать.

Кроме того, сборку мусора можно выполнить в отдельном потоке (хотя это может быть немного сложнее).На машинах с большим объемом оперативной памяти и для процессов, которые используют память очень медленно, возможно, вам вообще не захочется выполнять сборку мусора!Подсчет ссылок был бы некоторым недостатком с точки зрения производительности...

Другие советы

На самом деле подсчет ссылок и стратегии, используемые Sun JVM, — это разные типы алгоритмов сборки мусора.

Существует два основных подхода к отслеживанию мертвых объектов:трассировка и подсчет ссылок.При трассировке сборщик мусора начинает с «корней» — таких вещей, как ссылки на стек, и отслеживает все доступные (живые) объекты.Все, до чего невозможно добраться, считается мертвым.При подсчете ссылок каждый раз, когда ссылка изменяется, счетчик задействованных объектов обновляется.Любой объект, счетчик ссылок которого равен нулю, считается мертвым.

Практически во всех реализациях 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, которые могут работать в отдельном потоке и имеют все эти сумасшедшие оптимизации и т. д.Различия в том, как вы пишете код, должны быть незначительными и в идеале отсутствовать.

Я думаю, что статья "Теория и практика Java:Краткая история сбора мусора» от IBM должен помочь объяснить некоторые ваши вопросы.

Сбор мусора выполняется быстрее (эффективнее по времени), чем подсчет ссылок, если у вас достаточно памяти.Например, копирующий gc проходит по «живым» объектам и копирует их в новое пространство, а также может восстановить все «мертвые» объекты за один шаг, пометив всю область памяти.Это очень эффективно, если у тебя достаточно памяти.Коллекции поколений используют знание о том, что «большинство объектов умирают молодыми»;часто приходится копировать лишь несколько процентов объектов.

[Это также причина, по которой gc может быть быстрее, чем malloc/free]

Подсчет ссылок гораздо эффективнее использует пространство, чем сбор мусора, поскольку он освобождает память в тот самый момент, когда она становится недоступной.Это удобно, если вы хотите прикрепить финализаторы к объектам (например,закрыть файл, если объект File становится недоступным).Система подсчета ссылок может работать даже тогда, когда свободно лишь несколько процентов памяти.Но затраты на управление, связанные с необходимостью увеличивать и уменьшать счетчики при каждом назначении указателя, требуют много времени, а для освобождения циклов по-прежнему требуется какая-то сборка мусора.

Итак, компромисс очевиден:если вам приходится работать в среде с ограниченным объемом памяти или если вам нужны точные финализаторы, используйте подсчет ссылок.Если у вас достаточно памяти и вам нужна скорость, используйте сбор мусора.

Одним из больших недостатков отслеживающего сборщика мусора в Java является то, что время от времени он «останавливает мир» и замораживает приложение на относительно долгое время, чтобы выполнить полный сборщик мусора.Если куча большая и дерево объектов сложное, оно зависнет на несколько секунд.Кроме того, каждый полный сборщик мусора снова и снова посещает все дерево объектов, что, вероятно, весьма неэффективно.Еще один недостаток способа, которым Java выполняет сбор мусора, заключается в том, что вы должны сообщить jvm, какой размер кучи вы хотите (если значение по умолчанию недостаточно);JVM извлекает из этого значения несколько пороговых значений, которые запускают процесс GC, когда в куче скапливается слишком много мусора.

Я предполагаю, что это на самом деле основная причина дерганья Android (на основе Java) даже на самых дорогих мобильных телефонах по сравнению с плавностью работы iOS (на основе ObjectiveC и с использованием RC).

Мне бы хотелось увидеть опцию jvm, позволяющую включать управление памятью RC и, возможно, оставлять GC только для запуска в крайнем случае, когда памяти больше не осталось.

Последняя виртуальная машина Sun Java на самом деле имеет несколько алгоритмов GC, которые вы можете настроить.В спецификациях Java VM намеренно не указано фактическое поведение GC, чтобы разрешить разные (и несколько) алгоритмы GC для разных виртуальных машин.

Например, для всех людей, которым не нравится подход «остановить мир» стандартного поведения Sun Java VM GC, существуют такие виртуальные машины, как WebSphere Real Time от IBM который позволяет приложениям реального времени работать на Java.

Поскольку спецификация виртуальной машины Java общедоступна, (теоретически) ничто не мешает кому-либо реализовать виртуальную машину Java, использующую алгоритм GC CPython.

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

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

Поздно, но я думаю, что одним из важных аргументов в пользу RC в Python является его простота.Видеть это письмо от Алекса Мартелли, например.

(Я не смог найти ссылку вне кеша Google, дата электронного письма от 13 октября 2005 г. в списке Python).

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top