— Привет, Амиго!
— Привет, Риша!
— Ну, как день прошел?
— Отлично! Мне сегодня Билаабо рассказал про рекурсию, а Элли — про слабые и мягкие ссылки.
— А про призрачные ссылки рассказывала?
— Ты про PhantomReference? Упоминала, но не рассказывала подробно.
— Отлично, тогда надеюсь, ты не будешь против, если я заполню этот пробел.
— Конечно, я с удовольствием тебя послушаю, Риша!
— Отлично. Тогда я начну.
Призрачные (Phantom) ссылки – это самые слабые ссылки из всех. Только если на объект не остаётся никаких ссылок вообще, кроме призрачных, их механизм вступает в действие.
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();
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, и объект никогда не попадет в очередь призраков!
— Блин. Ладно, сдаюсь. Нельзя так нельзя.
— Вот и отлично. Надеюсь, ты вынесешь для себя что-то ценное из сегодняшнего урока.
— Да тут столько нового материала. А я думал, что уже все знаю. Спасибо тебе за урок, Риша.
— Пожалуйста. Все, иди отдыхай. Но не забудь, у нас вечером еще урок.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ