Как сборщик мусора Python обрабатывает циклические ссылки между объектами
Что проверяют
Для Java-разработчика этот вопрос обычно про сравнение: понимаете ли вы разницу между подсчётом ссылок и трассирующим сборщиком. Следом почти наверняка спросят, почему в JVM циклы вообще не проблема.
Как отвечать
Основной механизм CPython это подсчёт ссылок. У каждого объекта есть счётчик, при обнулении объект освобождается сразу, без пауз. Два объекта, ссылающиеся друг на друга, счётчик до нуля не доведут, поэтому поверх работает циклический сборщик, модуль gc.
Устроен он не как mark-and-sweep от корней, и это важная деталь:
- что отслеживается: только контейнеры, способные ссылаться на другие объекты (списки, словари, экземпляры классов). Числа и строки сборщик не обходит;
- поиск циклов: сборщик копирует счётчики группы контейнеров и вычитает ссылки, идущие изнутри группы. У кого остаток больше нуля, на того ссылаются снаружи: он жив вместе со всем, что от него достижимо. Остальное мусорный цикл;
- поколения: новые объекты проверяются часто, пережившие сборку реже, пороги видны через
gc.get_threshold(); - финализаторы: с Python 3.4 (PEP 442) циклы с
__del__тоже собираются.
Теперь JVM. Сборщики HotSpot (G1 по умолчанию, ZGC, Parallel) трассирующие: обходят граф от GC roots, то есть стеков потоков, статических полей, JNI-ссылок. Всё недостижимое мусор, и цикл, на который никто не ссылается снаружи, при обходе просто не будет помечен. Подсчёта ссылок в JVM нет, отдельный механизм для циклов не нужен. Цена разная: Python освобождает большую часть памяти сразу и предсказуемо, JVM копит мусор и убирает пачками, зато без накладных расходов на каждое присваивание.
Где спотыкаются
Описывают Python так же, как Java: трассировка от корней. Это описание JVM, а не CPython.
Считают, что gc.disable() выключает управление памятью. Отключается только поиск циклов, подсчёт ссылок работает дальше.
Переносят страх циклов на Java и разрывают их вручную через WeakReference. В JVM утечка возникает не из-за цикла, а из-за живой ссылки, например из статической коллекции или кеша.