phantom Reference

Открыта

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

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

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

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

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

— Ты про 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, и объект никогда не попадет в очередь призраков!

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

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

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

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

Комментарии (98)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
ShadyLine
Уровень 51
4 марта, 07:26
непонятно
Роман
Уровень 47
14 апреля 2025, 08:03
важное замечание: сильная ссылка исчезает, если x выходит из зоны видимости
SomeBody098веник в бане
7 октября 2024, 16:35
Но понимание – это очень уж странный предмет… Всякая вещь – или есть, или нет, – А понимание (я никак не пойму, в чём секрет!)… Понимание – если есть, то его сразу нет!
И. Ж.
Уровень 41
2 декабря 2023, 12:40
Непонятно если вернуть обьект у этой ссылки нельзя и вызвать метод, тогда зачем вообще добавлять в очередь и какой смысл всего этого
Denis Odesskiy Full Stack Developer
1 ноября 2024, 22:50
Смысл, безопасно удалить объект и убедиться в этом.
Rolik
Уровень 41
30 мая 2023, 13:43
finalize() - устаревший (deprecated) метод. Его использование не рекомендуется.
10 марта 2024, 06:36
Лекции немного устаревшие
Denis Odesskiy Full Stack Developer
1 ноября 2024, 22:47
Ну да, если finalize() удалят нас будет ждать как альтернатива веселая жизнь с фантомными ссылками и вот этим static class PhantomInteger extends PhantomReference<Integer> ради метода close()? А если мне этот класс нужно унаследовать еще от чего-то? Лучше уж указатели и деструкторы в С++😏
Dima Darienko
Уровень 47
6 марта 2023, 20:21
чаво каво
Василий Чи
Уровень 1
16 января 2023, 11:47
Grock
Уровень 44
4 апреля 2023, 08:09
См. еще инфо посвежее: https://openjdk.org/jeps/421
comrade_b
Уровень 39
29 сентября 2022, 11:13
Как обычно непонятно практическое применение. Вообще. Если про софт ссылки еще более или менее можно себе что-то представить, то дальше вообще нет. Задачи такого понимания не прибавляют.
Anonymous #3036451
Уровень 37
24 августа 2022, 14:07
//взываем сборщик мусора, надеемся, что он нас послушается :)
//он должен убить все «призрачно достижимые» объекты и поместить их в очередь
//призраков
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, 08:17
Шалом, Риша!