Научитесь эффективно использовать reference classes (ссылочные классы)

Автор: content Понедельник, Апрель 9th, 2012 Нет комментариев

Рубрика: Язык Java

Платформа Java2 представила пакет java.lang.ref содержащий классы, позволяющие Вам обращаться к объектам, не содержащимся в памяти. Данные классы также обеспечивают ограниченное взаимодействие со «сборщиком мусора». В этой статье Питер Хаггар исследует функциональные возможности, и поведение классов SoftReference, WeakReference, и PhantomReference и рекомендует некоторые правила по их использованию.

Впервые, когда пакет java.lang.ref включающий классы SoftReference, WeakReference, и PhantomReference был представлен в Java2, его значительность была преувеличена. Классы, содержащиеся в нем, могут быть полезны, но они имеют ряд ограничений, сужающих их применение для решения определенного количества задач.

Краткий обзор «сборщика мусора»

Главной особенностью reference classes (ссылочных классов) является их способность обратиться к объекту, который уже может быть объявлен для обработки его «сборщиком мусора». До того как были представлены reference classes, были доступны только прямые ссылки на объекты.

К примеру, следующая строка кода представляет прямую ссылку на объект obj:

 Object obj = new Object(); 

Данная ссылка резервирует память под объект obj. До тех пор, пока ссылка на obj существует, «сборщик мусора» не освободит память, зарезервированную под него.

Когда obj перестает использоваться, либо же явно указывается пустым (null) – объект становится доступным для «сборщика мусора», что значит, что никаких ссылок на него нет. Однако, следует отметить одну важную вещь: даже если объект после «обнуления» ссылки доступен для «сборщика мусора» — это не значит, что он будет убран при первом же его («сборщике») запуске, поскольку алгоритм работы «сборщика» постоянно изменяется. Некоторые алгоритмы анализируют «долгоживущие» объекты реже, нежели объекты, имеющие небольшую продолжительность жизни в программе («маложивущие») Таким образом, объект, доступный для «сборщика» может быть так никогда и не убран. Данная ситуация может иметь место, если программа заканчивается до того как «сборщик мусора» начинает очищать память от не использующихся объектов. Поэтому вы никогда не можете гарантировать, что доступный «сборщику мусора» объект будет действительно убран.

Эта информация может оказаться важной, поскольку мы пытаемся проанализировать работу ссылочных классов. Они могут оказаться очень полезными для решения специфических задач, однако из-за особенностей работы «сборщика мусора», с первого взгляда невозможно оценить все их полезные моменты. SoftReference, WeakReference и PhantomReference предполагают три различных способа обращения к объектам, без предотвращения уборки их «сборщиком мусора». Каждый тип ссылки на объект имеет свой собственный тип поведения и свое отличное от других классов взаимодействие со «сборщиком мусора». Вдобавок ко всему этому, все новые ссылочные классы представляют более «мягкий» (гибкий) вид ссылки, нежели обычная прямая ссылка. Кроме того, объектам, содержащимся в памяти, могут передаваться множественные (SoftReference, WeakReference и PhantomReference) ссылки.

Перед тем как идти дальше, познакомимся с некоторой терминологией:

Strongly reachable (прямо доступный) – к такому объекту можно получить доступ только посредством прямой ссылки.

Softly reachable (мягко доступный) – к такому объекту нельзя получить доступ через прямую ссылку, он может быть доступен только посредством SoftReference.

Weakly reachable (слабо доступный) – не может быть доступным через прямую и SoftReference ссылки. К такому объекту можно обратиться лишь посредством WeakReference.

Phantomly reachable (фантомно дотупный) – объект соответственно не может быть доступен посредством SoftReference и WeakReference, поскольку его использование было завершено. Обратиться к нему можно лишь при помощи PhantomReference ссылки.

Clear – установка полей объекта в null и объявление объекта, к которому происходит обращение, как finalizable.

Класс SoftReference

Типичное использование SoftReference class – обращение в кэш памяти. Идея состоит в том, что вы ссылаетесь на объект посредством SoftReference и все эти ссылки будут гарантированно очищены прежде чем JVM сообщит о завершении очистки памяти «сборщиком мусора». Основной момент заключается в том, что при запуске «сборщика мусора» объекты доступные посредством SoftReference и на которые вы ссылаетесь могут быть убраны, а могут быть и не убраны. Будет ли объект убран «сборщиком», или нет – зависит от его («сборщика») алгоритма в данном цикле работы и от объема свободной памяти в этот момент.

Класс WeakReference

«Слабые» ссылки также полезны для объектов, которые могут иметь либо продолжительный цикл жизни, либо легко быть созданы заново. Основная идея в том, что если «сборщик мусора» найдет объект, который может быть доступен посредством WeakReference – то он уберет его. Однако заметьте, что может потребоваться несколько запусков «сборщика мусора» прежде чем он обнаружит подобный объект.

Класс PhantomReference

PhantomReference class полезен в том случае, когда необходимо сохранить след объекта до того как он будет удален «сборщиком». Фантомная ссылка должна использоваться совместно с классом ReferenceQueue. ReferenceQueue необходим, поскольку он выполняет роль механизма предупреждения. Когда «сборщик мусора» находит объект, который может быть «фантомно-доступным» — этот объект помещается в свой класс ReferenceQueue. Помещение «фантомно-доступного» объекта в ReferenceQueue свидетельствует о том, что данный объект не используется и «сборщик мусора» готов его убрать из памяти. Это позволяет вам принять меры еще до того как в памяти произойдет очистка объекта.

Взаимодействие сборщика мусора и ссылок

Каждый раз «сборщик мусора» после запуска произвольно освобождает память, на которую больше нет прямых ссылок. Если же «сборщик мусора» обнаруживает объект, которые могут быть доступны посредством SoftReference , происходит следующее:

 

  • поля объекта, который может быть доступен через SoftReference, устанавливаются в null, что делает данный объект недоступным посредством прямой ссылок.
  • данный объект объявляется finalizable.
  • после того, как был запущен метод finalize()и память, зарезервированная под объект, освобождена – объект помещается в ReferenceQueue, если ReferenceQueue существует.

Если же «сборщик мусора» обнаруживает объект, которые могут быть доступны посредством WeakReference, происходит следующее:

  • поля объекта, который может быть доступен через WeakReference, устанавливаются в null, что делает данный объект недоступным посредством прямой ссылки.
  • данный объект объявляется finalizable.
  • после того, как был запущен метод finalize()и память, зарезервированная под объект, освобождена – объект помещается в ReferenceQueue, если он существует.

Если же «сборщик мусора» обнаруживает объект, которые могут быть доступны посредством «фантомной» ссылки, происходит следующее:

  • объект доступный посредством «фантомной» ссылки объявляется finalizable.
  • в отличии от «мягкой» и «слабой» ссылки – фантомная ссылка добавляется к своему ReferenceQueue перед тем как освободится память.(Помните, что все фантомные ссылки должны создаваться с соответствующими ReferenceQueue.) Это дает возможность предпринять какие-то действия до того, как «сборщик мусора» освободит память.

Посмотрите на следующий код и на рис.1, объясняющий его работуЛистинг1. Пример кода, использующего WeakReference и ReferenceQueue

//Создает прямую ссылку на объект
MyObject obj = new MyObject();                  //1

//Создает reference queue
ReferenceQueue rq = new ReferenceQueue();       //2

//Создает weakReference к obj и соответствующий наш reference queue
WeakReference wr = new WeakReference(obj, rq);  //3

Рис 1. Расположение объектов после выполнения линий //1, //2, и //3 кода в Листинге 1.Рис. 1 демонстрирует состояние объекта после выполнения каждой строки кода. Строка //1 создает объект MyObject, вторая строка //2 создает объект ReferenceQueue.Строка //3 создает объект WeakReference который ссылается на MyObject и ReferenceQueue. Заметьте что все ссылки obj, rq, и wr прямые. Чтобы воспользоваться всеми преимуществами ссылочных классов вы должны разрушить прямую ссылку к объекту MyObject установкой obj в null. Помните, что если вы не сделаете этого, то объект MyObject никогда не будет убран, сводя к нулю все преимущества ссылочных классов.

Каждый из ссылочных классов имеет свой метод get(), а класс ReferenceQueue имеет свой poll()метод. Метод get()возвращает ссылку на ссылаемый объект. Вызов get() в PhantomReference всегда возвращает null. Потому что он используется только для того чтобы определить намерения очистки. Метод poll()возвращает ссылочный объект, который был добавлен в queue и null если в queue ничего нет. Поэтому в результате вызова методов get()и poll()после выполнения кода, представленного в Листинге 1 произойдет следующее:

 Wr.get(); //возвращает ссылку на MyObject Rq.poll(); //возвращает null 

Теперь, предположим, запущен «сборщик мусора». Методы get()и poll()возвращают одну и ту же величину, поскольку объект MyObject не удален; на obj все еще указывает прямая ссылка. Фактически, расположение объектов не меняется и напоминает Рис.1. Однако, посмотрите на этот код:

 obj = null; System.gc(); //запуск «сборщика мусора» 

После его выполнения расположение объекта изменится следующим образом:


Рис 2. Расположение объекта после obj = null; и запуска «уборщика мусора»Теперь, вызов get() и poll() дает различные результаты:

 wr.get(); //возвращает null rq.poll(); //возвращает ссылку на объект WeakReference 

Данная ситуация свидетельствует о том, что объект MyObject на который изначально указывала ссылка объекта WeakReference больше не доступен. Это значит, что «сборщик мусора» очистил память, которая использовалась для его хранения, и поэтому объект WeakReference был помещен в свой ReferenceQueue. Теперь вы знаете что объект был объявлен finalizable и после того как метод get() класса WeakReference или класса SoftReference вернул null возможно, но не обязательно был убран «уборщиком мусора». Только после того как объект был объявлен finalize и «сборщик мусора» очистил память, использовавшуюся для его хранения, WeakReference или SoftReference помещается в соответствующий ReferenceQueue. В листинге 2 приведена полностью рабочая программа, демонстрирующая некоторые из этих принципов. Код относительно очевидный, с множеством комментариев и выводящий текущее состояние объектов для большей ясностиЛистинг 2. Рабочая программа, демонстрирующая механику работы Reference классов.

import java.lang.ref.*;
class MyObject
{
protected void finalize() throws Throwable
{
System.out.println(«In finalize method for this object: » +
this);
}
}

class ReferenceUsage
{
public static void main(String args[])
{
hold();
release();
}

public static void hold()
{
System.out.println(«Example of incorrectly holding a strong » +
«reference»);
//Create an object
MyObject obj = new MyObject();
System.out.println(«object is » + obj);

//Create a reference queue
ReferenceQueue rq = new ReferenceQueue();

//Create a weakReference to obj and associate our reference queue
WeakReference wr = new WeakReference(obj, rq);

System.out.println(«The weak reference is » + wr);

//Check to see if it’s on the ref queue yet
System.out.println(«Polling the reference queue returns » +
rq.poll());
System.out.println(«Getting the referent from the » +
«weak reference returns » + wr.get());

System.out.println(«Calling GC»);
System.gc();
System.out.println(«Polling the reference queue returns » +
rq.poll());
System.out.println(«Getting the referent from the » +
«weak reference returns » + wr.get());
}

public static void release()
{
System.out.println(«»);
System.out.println(«Example of correctly releasing a strong » +
«reference»);
//Create an object
MyObject obj = new MyObject();
System.out.println(«object is » + obj);

//Create a reference queue
ReferenceQueue rq = new ReferenceQueue();

//Create a weakReference to obj and associate our reference queue
WeakReference wr = new WeakReference(obj, rq);

System.out.println(«The weak reference is » + wr);

//Check to see if it’s on the ref queue yet
System.out.println(«Polling the reference queue returns » +
rq.poll());
System.out.println(«Getting the referent from the » +
«weak reference returns » + wr.get());

System.out.println(«Set the obj reference to null and call GC»);
obj = null;
System.gc();
System.out.println(«Polling the reference queue returns » +
rq.poll());
System.out.println(«Getting the referent from the » +
«weak reference returns » + wr.get());
}
}

Использование и идиомы

Идея использования всех этих классов состоит в том чтобы не хранить объект в памяти на протяжении всего цикла работы приложения. Вместо этого, вы ссылаетесь на объект посредством WeakReference, SoftReference, PhantomReference разрешая «сборщику мусора» убрать его из памяти. Такое использование данных классов может быть полезным в том случае, если вы хотите уменьшить количество памяти, используемое во время работы приложения. Вы должны помнить, что использование этих классов возможно лишь при отсутствии прямой ссылки на объект. Если вы будете использовать прямую ссылку – вы не сможете воспользоваться преимуществами ссылочных классов.

Кроме того, вы должны выбрать правильный способ для того чтобы перед использованием данных классов проверить, собирается ли «сборщик мусора» очистить память объекта, и если да – вы должны восстановить его. Данная задача может быть решена несколькими способами. Важно выбрать правильный способ, поскольку выбор неправильного способа для решения данной задачи может привести к ряду неприятностей. Посмотрите один из способов решения приведенный в листинге 3.
Листинг 3. Способ восстановления референта.

obj = wr.get();
if (obj == null)
{
wr = new WeakReference(recreateIt());  //1
obj = wr.get();                        //2
}
// код, работающий с объектом obj

После изучения данного примера – рассмотрите альтернативный способ восстановления объекта референта от WeakReference:
Листнг 4. Альтернативный способ получения объекта референта.

obj = wr.get();
if (obj == null)
{
obj = recreateIt();                    //1
wr = new WeakReference(obj);           //2
}
//код, работающий с объектом obj

Сравните эти два способа на предмет того, сможете ли вы определить какой из них гарантированно будет работать. Способ, приведенный в Листинге 3 не гарантирует работу во всех случаях, в отличии от кода в Листинге 4. Причина несовершенности кода, приведенного в листинге 2 заключается в том, что объект obj после выполнения блока if может так и не оказаться равным null. Посмотрите что произойдет, если «сборщик мусора» запустится после строки //1 в Листинге 3 но до того как начнет выполняться строка //2. Метод recreateIt()восстанавливает объект, но в ссылке WeakReference, а не в прямой ссылке. Поэтому, если«сборщик», запущен до выполнения строки //2 которая передает объекту прямую ссылку, объект будет потерян, и метод wr.get()вернет null.

В листинге 4 этого не случится, поскольку строка //1 восстанавливает объект и назначает сразу же ему прямую ссылку. Поэтому «уборщик мусора», запущенный после выполнения этой строки, но до выполнения строки //2 – не уберет объект из памяти. Затем ссылка WeakReference будет создана в строке //2. После выполнения if-блока вы должны установить значение объекта в null, для того чтобы дать возможность «сборщику» убрать объект а вам затем в полной мере воспользоваться преимуществом класса WeakReference. В листинге 5 приведена полностью рабочая программа, демонстрирующая отличия только что описанных способов.(Для того, чтобы запустить данный пример, файл «temp.fil» должен находиться в директории из которой запускается данная программа.)
Листинг 5. Рабочая программа, демонстрирующая правильный и неправильный способы, описанные выше.

import java.io.*;
import java.lang.ref.*;

class ReferenceIdiom
{
public static void main(String args[]) throws FileNotFoundException
{
broken();
correct();
}

public static FileReader recreateIt() throws FileNotFoundException
{
return new FileReader(«temp.fil»);
}

public static void broken() throws FileNotFoundException
{
System.out.println(«Executing method broken»);
FileReader obj = recreateIt();
WeakReference wr = new WeakReference(obj);

System.out.println(«wr refers to object » + wr.get());

System.out.println(«Now, clear the reference and run GC»);
//Clear the strong reference, then run GC to collect obj.
obj = null;
System.gc();

System.out.println(«wr refers to object » + wr.get());

//Now see if obj was collected and recreate it if it was.
obj = (FileReader)wr.get();
if (obj == null)
{
System.out.println(«Now, recreate the object and wrap it
In a WeakReference»);
wr = new WeakReference(recreateIt());
System.gc();  //FileReader object is NOT pinned…there is no
//strong reference to it.  Therefore, the next
//line can return null.
obj = (FileReader)wr.get();
}
System.out.println(«wr refers to object » + wr.get());
}

public static void correct() throws FileNotFoundException
{
System.out.println(«»);
System.out.println(«Executing method correct»);
FileReader obj = recreateIt();
WeakReference wr = new WeakReference(obj);

System.out.println(«wr refers to object » + wr.get());

System.out.println(«Now, clear the reference and run GC»);
//Clear the strong reference, then run GC to collect obj
obj = null;
System.gc();

System.out.println(«wr refers to object » + wr.get());

//Now see if obj was collected and recreate it if it was.
obj = (FileReader)wr.get();
if (obj == null)
{
System.out.println(«Now, recreate the object and wrap it
in a WeakReference»);
obj = recreateIt();
System.gc();  //FileReader is pinned, this will not affect
//anything.
wr = new WeakReference(obj);
}
System.out.println(«wr refers to object » + wr.get());
}
}

Резюме.

Ссылочные классы могут оказаться достаточно полезными, при условии использования их в нужных ситуациях. Однако гарантия их корректной работы в некоторой степени ограничивается тем фактом, что их работа полагается на непредсказуемое поведение «сборщика мусора». Эффективность использования этих классов также в значительной степени зависит от правильности выбранного способа для программирования их работы; крайне важно понять как работают данные классы для того чтобы правильно работать с ними.

Источник: http://www.javaportal.ru/java/articles/referenceclasses.html
Автор: Peter Haggar

 

Оставить комментарий

Чтобы оставлять комментарии Вы должны быть авторизованы.

Похожие посты