JavaRush /Курсы /Java Collections /phantom Reference

phantom Reference

Java Collections
4 уровень , 7 лекция
Открыта

— Привет, Амиго!

— Привет, Риша!

— Ну, как день прошел?

— Отлично! Мне сегодня Билаабо рассказал про рекурсию, а Элли — про слабые и мягкие ссылки.

— А про призрачные ссылки рассказывала?

— Ты про PhantomReference? Упоминала, но не рассказывала подробно.

— Отлично, тогда надеюсь, ты не будешь против, если я заполню этот пробел.

— Конечно, я с удовольствием тебя послушаю, Риша!

— Отлично. Тогда я начну.

Призрачные (Phantom) ссылки – это самые слабые ссылки из всех. Только если на объект не остаётся никаких ссылок вообще, кроме призрачных, их механизм вступает в действие.

phantom Reference - 1

PhantomReference используется для сложной процедуры удаления объекта. Это может быть необходимо, когда объект что-то делает за границами Java-машины, например вызывает низкоуровневые функции ОС или пишет свое состояние в файл или еще что-нибудь очень важное.

Пример использования:

Пример создания призрачных ссылок
//специальная очередь для призрачных объектов
ReferenceQueue<Integer> queue = new ReferenceQueue<Integer>();

//список призрачных ссылок
ArrayList<PhantomReference<Integer>> list = new ArrayList<PhantomReference<Integer>>();

//создаем 10 объектов и добавляем их в список через призрачные ссылки
for ( int i = 0; i < 10; i++) 
{
 Integer x = new Integer(i);
 list.add(new PhantomReference<Integer>(x, queue));
}

Еще раз обращаю внимание на последнюю строчку. В PhantomReference передается не только объект x, но и специальная очередь призрачных ссылок.

— А зачем нужна эта очередь?

— Вот сейчас и расскажу.

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

— А это как?

— Тут довольно много нюансов, так что начну с самого простого.

Если на объект остаются только призрачные ссылки, то вот что его ждет:

Шаг 1. Во время ближайшей сборки мусора у объекта будет вызван метод finalize(). Но, если метод finalize() не был переопределен, этот шаг пропускается, а выполнится сразу шаг 2.

Шаг 2. Во время следующей сборки мусора, объект будет помещен в специальную очередь призрачных объектов, из которой будет удален, когда у PhantomReference вызовут метод clear().

— А кто его вызовет? Ведь объект-то как бы удален, разве нет?

— Тут дело в том, что фактически объект умер в нашем (Java) мире, но не исчез, а остался в нем призраком – на него хранится ссылка в очереди призрачных объектов. Та самая ReferenceQueue, ссылку на которую мы так заботливо передаем в конструктор PhantomReference.

— Т.е. эта ReferenceQueue — это как бы потусторонний мир?

— Скорее, как мир призраков.

И чтобы удалить объект-призрак, надо вызвать clear() у его призрачной ссылки.

Вот как можно продолжить предыдущий пример:

Пример создания призрачных ссылок
//специальная очередь для призрачных объектов
ReferenceQueue<Integer> queue = new ReferenceQueue<Integer>();

//список призрачных ссылок
ArrayList<PhantomReference<Integer>> list = new ArrayList<PhantomReference<Integer>>();

//создаем 10 объектов и добавляем их в список через призрачные ссылки
for ( int i = 0; i < 10; i++)
{
 Integer x = new Integer(i);
 list.add(new PhantomReference<Integer>(x, queue));
}

//взываем сборщик мусора, надеемся, что он нас послушается :)
//он должен убить все «призрачно достижимые» объекты и поместить их в очередь
//призраков
System.gc();

//достаем из очереди все объекты
Reference<? extends Integer>referenceFromQueue;
while ((referenceFromQueue = queue.poll()) != null) 
{
 //выводим объект на экран
 System.out.println(referenceFromQueue.get());
 //очищаем ссылку
 referenceFromQueue.clear();
}

— Что что-то тут происходит – это понятно. Даже почти понятно, что именно происходит.

Но как это использовать на практике?

— Вот тебе более адекватный пример:

Пример создания призрачных ссылок
//специальная очередь для призрачных объектов
ReferenceQueue<Integer> queue = new ReferenceQueue<Integer>();

//список призрачных ссылок
ArrayList<PhantomInteger> list = new ArrayList<PhantomInteger>();

//создаем 10 объектов и добавляем их в список через призрачные ссылки
for ( int i = 0; i < 10; i++) 
{
 Integer x = new Integer(i);
 list.add(new PhantomInteger (x, queue));
}
Эта нить будет следить за призрачной очередью и удалять оттуда объекты
Thread referenceThread = new Thread()
{
 public void run()
 {
  while (true)
  {
   try
   {
    //получаем новый объект из очереди, если объекта нет - ждем!
    PhantomInteger ref = (PhantomInteger)queue.remove();
    //вызвваем у него метод close
    ref.close();
    ref.clear();
   }
   catch (Exception ex)
   {
    // пишем в лог ошибки
   }
  }
 }
};
//запускаем поток в служебном режиме.
referenceThread.setDaemon(true);
referenceThread.start();
Это класс, унаследованный от PhantomReference, у него есть метод close()
static class PhantomInteger extends PhantomReference<Integer>
{
 PhantomInteger(Integer referent, ReferenceQueue<? super Integer> queue)
 {
  super(referent, queue);
 }

 private void close()
 {
  System.out.println("Bad Integer totally destroyed!");
 }
}

Мы тут сделали три вещи.

Во-первых, мы создали класс PhantomInteger, который унаследовали от PhantomReference<Integer>.

Во-вторых, у этого класса есть специальный метод – close(), ради вызова которого как бы все это и затевается.

В третьих, мы объявили специальную нить — referenceThread. Она в цикле ждет, пока в очереди призраков не появится еще один объект. Как только он появляется, она удаляет его из очереди призраков, а затем вызывает у него метод close(). А затем метод clear(). И все – призрак может переходить в следующий лучший мир. В нашем он нас больше не побеспокоит.

— Как интересно, однако все вышло.

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

Но, учти, ты не можешь вызвать метод самого объекта. Ссылку на него получить нельзя! Метод get() у PhantomReference всегда возвращает null.

— Но ведь мы же наследуемся от PhantomReference!

— Даже внутри наследника PhantomReference, метод get() возвращает null.

— Тогда я просто сохраню ссылку на объект в конструкторе

— Ага. Но тогда эта ссылка будет StrongReference, и объект никогда не попадет в очередь призраков!

— Блин. Ладно, сдаюсь. Нельзя так нельзя.

— Вот и отлично. Надеюсь, ты вынесешь для себя что-то ценное из сегодняшнего урока.

— Да тут столько нового материала. А я думал, что уже все знаю. Спасибо тебе за урок, Риша.

— Пожалуйста. Все, иди отдыхай. Но не забудь, у нас вечером еще урок.

Комментарии (97)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Роман Уровень 47
14 апреля 2025
важное замечание: сильная ссылка исчезает, если x выходит из зоны видимости
SomeBody098 Уровень 51
7 октября 2024
Но понимание – это очень уж странный предмет… Всякая вещь – или есть, или нет, – А понимание (я никак не пойму, в чём секрет!)… Понимание – если есть, то его сразу нет!
И. Ж. Уровень 41
2 декабря 2023
Непонятно если вернуть обьект у этой ссылки нельзя и вызвать метод, тогда зачем вообще добавлять в очередь и какой смысл всего этого
Denis Odesskiy Уровень 47
1 ноября 2024
Смысл, безопасно удалить объект и убедиться в этом.
Rolik Уровень 41
30 мая 2023
finalize() - устаревший (deprecated) метод. Его использование не рекомендуется.
10 марта 2024
Лекции немного устаревшие
Denis Odesskiy Уровень 47
1 ноября 2024
Ну да, если finalize() удалят нас будет ждать как альтернатива веселая жизнь с фантомными ссылками и вот этим static class PhantomInteger extends PhantomReference<Integer> ради метода close()? А если мне этот класс нужно унаследовать еще от чего-то? Лучше уж указатели и деструкторы в С++😏
Dima Darienko Уровень 47
6 марта 2023
чаво каво
Василий Чи Уровень 51
16 января 2023
Grock Уровень 44
4 апреля 2023
См. еще инфо посвежее: https://openjdk.org/jeps/421
comrade_b Уровень 39
29 сентября 2022
Как обычно непонятно практическое применение. Вообще. Если про софт ссылки еще более или менее можно себе что-то представить, то дальше вообще нет. Задачи такого понимания не прибавляют.
Anonymous #3036451 Уровень 37
24 августа 2022

//взываем сборщик мусора, надеемся, что он нас послушается :)
//он должен убить все «призрачно достижимые» объекты и поместить их в очередь
//призраков
System.gc();

//достаем из очереди все объекты
Reference<? extends Integer>referenceFromQueue;
while ((referenceFromQueue = queue.poll()) != null)
{
 //выводим объект на экран
 System.out.println(referenceFromQueue.get());
 //очищаем ссылку
 referenceFromQueue.clear();
}
Метод referenceFromQueue.get() всегда вернет null и код работать не будет.
Anonymous #2957882 Уровень 1
8 июля 2022
Шалом, Риша!
Andrey Karelin Уровень 41
29 апреля 2022
Со ссылками Strong и Soft еще как-то более-менее понятно. С Phantom - с очень большой натяжкой. Для чего необходима Weak совсем не ясно. Если эти ссылки уничтожит ближайший сборщик мусора...который приходит сам по себе (по внутреннему механизму JVM), и КОГДА именно он это сделает совсем не прогнозируемо, то зачем нам оставлять такие ссылки? Все равно, что вынести мусорное ведро в бак с нужной вещью, и надеяться, что она вам понадобится раньше, чем баки вывезет мусоровозка.