Может ли garbage collector очистить циклические ссылки между объектами в Java
Что проверяют
Знаете ли вы, что сборщик мусора в Java и в Android Runtime работает по достижимости, а не по счётчику ссылок. Следом почти всегда спрашивают: раз циклы не страшны, откуда тогда утечки памяти в Android?
Как отвечать
Да, может. Сборщик JVM и ART не считает ссылки, а обходит граф объектов от корней (GC roots):
- стек: локальные переменные и параметры в активных кадрах всех потоков;
- статические поля: всё, на что ссылаются загруженные классы;
- потоки и мониторы: живые потоки и объекты, на которых держат блокировку;
- JNI-ссылки: объекты, удерживаемые нативным кодом.
Всё, до чего от корней не дотянуться, считается мусором, сколько бы ссылок ни было внутри группы. Два Node, указывающие друг на друга, после обнуления последней внешней ссылки становятся недостижимыми и собираются вместе. В ART работает конкурентный копирующий сборщик, но принцип достижимости тот же, что и в HotSpot.
Проблема циклов характерна для подсчёта ссылок: ARC в Swift и Objective-C, CPython (там для неё есть отдельный детектор циклов).
Затем переходите к Android. Утечка там означает, что ненужный объект остаётся достижимым от корня:
- статика и синглтоны: хранят Activity или её
Context; - отложенные задачи: анонимный
RunnableвHandler.postDelayed()неявно держит внешнюю Activity; - слушатели: зарегистрированы и не сняты в
onStop()илиonDestroy(); - фоновая работа: поток или корутина в
GlobalScopeпереживает экран.
Находят такие утечки LeakCanary и Memory Profiler в Android Studio: дамп кучи показывает путь от корня до утёкшего объекта.
Где спотыкаются
Говорят, что Java использует счётчик ссылок и поэтому циклы опасны. Это ошибка на уровне основ, и интервьюер её запомнит.
Советуют обнулять все поля или вызывать System.gc(). Обнуление внутри недостижимой группы ничего не меняет, а System.gc() лишь подсказка, которую рантайм вправе проигнорировать.
Лечат утечки через WeakReference везде подряд. Слабая ссылка уместна для слушателей и кешей, но чаще правильнее снять подписку по жизненному циклу. SoftReference для кешей документация Android не рекомендует, вместо неё берут LruCache.