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

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

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

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

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

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

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

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

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

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