Представьте, что к нам в гости приходил родственник с котиком. И этот котик оставил в нашем доме игрушку. Мы знаем, что котик точно больше не в доме, но его игрушка всё ещё занимает место.
Так вот "котик" — это объект, а "игрушка" — это ресурсы или память, которые объект использовал. Фантомная ссылка позволяет нам узнать, что котик (объект) ушёл и игрушку можно убрать. То есть, мы узнаем, что объект уже не используется и скоро будет окончательно удален, поэтому мы можем произвести какие-то действия (закрыть ресурсы, что-то очистить).
Как фантомные ссылки понимают, что котик ушел?
Фантомные ссылки управляются с помощью класса PhantomReference и ReferenceQueue и работают совместно со сборщиком мусора. Объект PhantomReference хранит ссылку на котика так, что эта ссылка не мешает сборке мусора. Когда котик (объект), на который указывает фантомная ссылка, становится доступным для сборки мусора, он автоматически помещается в ReferenceQueue.
Примерное описание работы:
- Сначала создаётся PhantomReference, куда передаётся сам котик, за которым нужно следить, и ссылочная очередь (ReferenceQueue). Тут есть очень важный момент, что фантомная ссылка на котика всегда возвращает null при попытке доступа через метод get(). Это сделано специально, чтобы мы не использовали объекты, которые должны быть собраны сборщиком мусора.
- Когда сборщик мусора определяет, что объект, на который указывает фантомная ссылка, может быть собран (то есть нет других сильных ссылок на этот объект, и он становится доступным для удаления), сборщик автоматически помещает котика в переданную очередь ссылок. Т.е. наш котик будет помещен в очередь, если он вышел из квартиры, хотя игрушка пока что осталась.
- Мы в коде можем самостоятельно проверять очередь ссылок. Если в очереди появляется ссылка, это означает, что "котик ушел" — объект был подготовлен к удалению, а значит мы можем выполнить какие-то действия по очистке ресурсов до того как память будет окончательно освобождена.
- Выполняем phantomReference.clear() и phantomReference = null, чтобы сказать, что мы готовы забыть котика и выполнили все действия по очистке.
Метод clear() очищает ссылку внутри PhantomReference. Так мы немного уменьшим количество работы для сборщика мусора, потому что ему не придется проверять состояние объекта (котика).
Установка phantomReference в null помогает в управлении памятью, так как после этого на PhantomReference больше нет активных ссылок, и он тоже может быть собран сборщиком мусора при следующем проходе.
Пример:
Первым шагом мы создали очередь ссылок ReferenceQueue. Эта очередь будет использоваться для получения уведомлений о том, что котики, связанные с фантомными ссылками, готовы к удалению.
Затем мы создаём самого котика и PhantomReference, которая привязывается к котику (объекту) и указывает на созданную очередь. Заметьте, что котик просто привязывается к очереди, но не помещается в неё.
После того как котик покинет наш дом и становится доступным для сборки мусора и сборщик мусора обрабатывает этот объект, фантомная ссылка автоматически помещается в ReferenceQueue.
Для отслеживания этого процесса нам нужно регулярно проверять содержимое ReferenceQueue через catQueue.poll().
Как только фантомная ссылка появляется в очереди, это сигнал о том, что объект был обработан сборщиком мусора. Теперь можно выполнить нужные действия для очистки ресурсов, связанных с объектом (выкинуть игрушку).
В моём примере есть проблема, потому что referenceQueue.poll() всегда возвращает null. Тут может быть одна из причин:
- Сборка мусора не происходит мгновенно или по строгому расписанию. Она зависит от множества факторов. Если наш объект ещё не был собран, его фантомная ссылка не будет помещена в очередь.
- Если на объект всё ещё существуют сильные ссылки, то объект не будет доступен для сборки мусора.
- В некоторых случаях, особенно в небольших или простых приложениях с достаточным объемом свободной памяти, сборщик мусора может не активироваться. Можно попробовать вызвать System.gc() для предложения JVM выполнить сборку мусора, хотя это и не гарантирует мгновенного выполнения. Вот мне как раз не помогло.
Когда использовать
Фантомные ссылки обычно используются в ситуациях, когда необходимо точно знать момент, когда игрушка (ресурсы объекта) становится доступной для сборки мусора. Если мы знаем такой момент, то сможем выполнить определённые операции по очистке или освобождению ресурсов, например, выкинуть игрушку или убрать её в шкаф.
Я ни разу не видела PhantomReference в реальных проектах, но погуглила где они могут быть полезны:
- Основное назначение фантомных ссылок — получение уведомления о том, что объект скоро будет полностью удален. Такие ссылки могут использоваться в библиотеках для профилирования или мониторинга, чтобы уведомлять о ситуациях, когда объекты, которые должны были быть удалены, все еще занимают память. Я, кстати, попробовала проверить в LeakCanary, но там не используют.
- Использование фантомных ссылок позволяет безопасно очистить ресурсы, которые не управляются сборщиком мусора, например, нативные ресурсы (такие как внешние библиотеки на С/C++ или какие-то драйверы). Мы сможем убедиться, что очистка происходит в нужный момент (не раньше и не позже).
- Так как фантомные ссылки помещаются в очередь ссылок (ReferenceQueue) только после финализации объекта, мы можем контролировать процесс очистки и убедиться, что все необходимые действия выполнены в правильном порядке.
Дубль статей в телеграмме — https://t.me/android_junior
Мои заметки в телеграмме — https://t.me/android_junior_notes
P.S. На самом деле можно ещё написать про finalize и много других моментов, но и так получилось слишком много.