Hello! На сегодняшнем занятии мы подробно поговорим о «фантомных linkх» (PhantomReference) в Java. What это за ссылки такие, почему называются «фантомными» и How ими пользоваться? Как ты помнишь, в Java есть 4 вида ссылок:
-
StrongReference (обычные ссылки, которые мы создаем при создании an object):
Cat cat = new Cat()
cat в этом примере — Strong-link.
-
SoftReference (мягкая link). У нас была лекция про эти ссылки.
-
WeakReference (слабая link). Про них тоже была лекция, вот.
-
PhantomReference (фантомная link).
SoftReference
, WeakReference
и PhantomReference
наследуются от класса Reference
. Наиболее важные методы при работе с этими классами:
-
get()
— возвращает an object, на который ссылается эта link; clear()
— удаляет ссылку на an object.
SoftReference
и WeakReference
. Важно помнить, что они работают по-разному с разными видами ссылок. Мы сегодня не будем подробно рассматривать первые три типа, а поговорим о фантомных linkх. Остальные виды ссылок мы тоже затронем, но только в той части, где будем говорить, чем фантомные ссылки от них отличаются. Поехали! :) Начнем с того, зачем нам вообще нужны фантомные ссылки. Как ты знаешь, освобождением памяти от ненужных an objectов Java занимается сборщик мусора (Garbage Collector or gc). Сборщик удаляет an object в два «прохода». В первый проход он только смотрит на an objectы, и, если надо, помечает его How «ненужный, подлежащий удалению». Если у этого an object был переопределен метод finalize()
, он вызывается. Или не вызывается — How повезет. Ты наверняка помнишь, что finalize()
— штука непостоянная :) Во второй проход сборщика an object удаляется, и память освобождается. Такое непредсказуемое поведение сборщика мусора создает для нас ряд проблем. Мы не знаем когда именно начнется работа сборщика мусора. Мы не знаем будет ли вызван метод finalize()
. Плюс ко всему, во время работы finalize()
может быть создана strong-link на an object, и тогда он вообще не будет удален. В системах, требовательных к объему свободной памяти, это может легко привести к OutOfMemoryError
. Все это подталкивает нас к использованию фантомных ссылок. Дело в том, что это меняет поведение сборщика мусора. Если на an object остались только фантомные ссылки, то у него:
-
вызывается метод
finalize()
(если он переопределен); -
если после работы
finalize()
ничего не изменилось и an object все еще может быть удален, фантомная link на an object помещается в специальную очередь —ReferenceQueue
.
clear()
. Давай рассмотрим пример. Для начала создадим тестовый класс, который будет хранить в себе Howие-то данные.
public class TestClass {
private StringBuffer data;
public TestClass() {
this.data = new StringBuffer();
for (long i = 0; i < 50000000; i++) {
this.data.append('x');
}
}
@Override
protected void finalize() {
System.out.println("У an object TestClass вызван метод finalize!!!");
}
}
Мы специально How следует «загружаем» an objectы данными при создании (добавляем в каждый an object по 50 миллионов символов «х»), чтобы занять побольше памяти. Кроме того, мы специально переопределяем метод finalize()
, чтобы увидеть, что он сработал. Далее нам понадобится класс, который будет наследоваться от PhantomReference
. Зачем нам нужен такой класс? Все просто. Так мы сможем добавить дополнительную логику к методу clear()
, чтобы увидеть, что очистка фантомной ссылки действительно произошла (а значит, an object удален).
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
public class MyPhantomReference<TestClass> extends PhantomReference<TestClass> {
public MyPhantomReference(TestClass obj, ReferenceQueue<TestClass> queue) {
super(obj, queue);
Thread thread = new QueueReadingThread<TestClass>(queue);
thread.start();
}
public void cleanup() {
System.out.println("Очистка фантомной ссылки! Удаление an object из памяти!");
clear();
}
}
Далее, нам понадобится отдельный поток, который будет ждать, пока сборщик мусора сделает свое дело, и в нашей очереди ReferenceQueue
появятся фантомные ссылки. Как только такая link попадет в очередь, у нее будет вызван метод cleanup()
:
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
public class QueueReadingThread<TestClass> extends Thread {
private ReferenceQueue<TestClass> referenceQueue;
public QueueReadingThread(ReferenceQueue<TestClass> referenceQueue) {
this.referenceQueue = referenceQueue;
}
@Override
public void run() {
System.out.println("Поток, отслеживающий очередь, стартовал!");
Reference ref = null;
//ждем, пока в очереди появятся ссылки
while ((ref = referenceQueue.poll()) == null) {
try {
Thread.sleep(50);
}
catch (InterruptedException e) {
throw new RuntimeException("Поток " + getName() + " был прерван!");
}
}
//How только в очереди появилась фантомная link - очистить ее
((MyPhantomReference) ref).cleanup();
}
}
И, наконец, нам понадобится метод main()
: вынесем его в отдельный класс Main
. В нем мы создадим an object TestClass
, фантомную ссылку на него и очередь для фантомных ссылок. После этого мы вызовем сборщик мусора и посмотрим, что будет :)
import java.lang.ref.*;
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread.sleep(10000);
ReferenceQueue<TestClass> queue = new ReferenceQueue<>();
Reference ref = new MyPhantomReference<>(new TestClass(), queue);
System.out.println("ref = " + ref);
Thread.sleep(5000);
System.out.println("Вызывается сборка мусора!");
System.gc();
Thread.sleep(300);
System.out.println("ref = " + ref);
Thread.sleep(5000);
System.out.println("Вызывается сборка мусора!");
System.gc();
}
}
Вывод в консоль: ref = MyPhantomReference@4554617c Поток, отслеживающий очередь, стартовал! Вызывается сборка мусора! У an object TestClass вызван метод finalize!!! ref = MyPhantomReference@4554617c Вызывается сборка мусора! Очистка фантомной ссылки! Удаление an object из памяти! What же мы здесь видим? Все произошло, How мы и планировали! У нашего класса an object был переопределен метод finalize()
, и он был вызван во время работы сборщика. Далее, фантомная link была помещена в очередь ReferenceQueue
. Там у нее был вызван метод clear()
(из которого мы сделали cleanup()
, чтобы добавить вывод в консоль). В итоге an object был удален из памяти. Теперь ты видишь, How именно это работает :) Конечно, тебе не нужно зазубривать наизусть всю связанную с фантомными linkми теорию. Но будет хорошо, если ты будешь помнить хотя бы главные моменты. Во-первых, это самые слабые ссылки из всех. Они вступают в работу только когда на an object не осталось ниHowих других ссылок. Список ссылок, которые мы привели выше, идет по «убыванию силы»: StrongReference
-> SoftReference
-> WeakReference
-> PhantomReference
Фантомная link вступит в бой только когда на наш an object не будет ни Strong, ни Soft, ни Weak ссылок :) Во-вторых, метод get()
для фантомной ссылки всегда возвращает null
. Вот простой пример, где мы создаем три разных типа ссылок для трех разных видов автомобилей:
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
public class Main {
public static void main(String[] args) {
Sedan sedan = new Sedan();
HybridAuto hybrid = new HybridAuto();
F1Car f1car = new F1Car();
SoftReference<Sedan> softReference = new SoftReference<>(sedan);
System.out.println(softReference.get());
WeakReference<HybridAuto> weakReference = new WeakReference<>(hybrid);
System.out.println(weakReference.get());
ReferenceQueue<F1Car> referenceQueue = new ReferenceQueue<>();
PhantomReference<F1Car> phantomReference = new PhantomReference<>(f1car, referenceQueue);
System.out.println(phantomReference.get());
}
}
Вывод в консоль: Sedan@4554617c HybridAuto@74a14482 null Метод get()
вернул вполне нормальные an objectы для мягкой ссылки и слабой ссылки, но вернул null
для фантомной. В-третьих, основная область использование фантомных ссылок -— сложные proceduresы удаления an objectов из памяти. Вот и все! :) На этом наше сегодняшнее занятие окончено. Но на одной теории далеко не уедешь, поэтому пора возвращаться к решению задач! :)
GO TO FULL VERSION