JavaRush /Java Blog /Random-TK /PhantomReference в Java

PhantomReference в Java

Toparda çap edildi
Hello! На сегодняшнем занятии мы подробно поговорим о «фантомных linkх» (PhantomReference) в Java. What это за ссылки такие, почему называются «фантомными» и How ими пользоваться? Как ты помнишь, в Java есть 4 вида ссылок:
  1. StrongReference (обычные ссылки, которые мы создаем при создании an object):

    Cat cat = new Cat()

    cat в этом примере — Strong-link.

  2. SoftReference (мягкая link). У нас была лекция про эти ссылки.

  3. WeakReference (слабая link). Про них тоже была лекция, вот.

  4. PhantomReference (фантомная link).

Объекты трех последних видов типизированные (например, SoftReference<Integer>, WeakReference<MyClass>). Классы 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.

Самое важное, что нужно понимать при работе с фантомными linkми, — an object не удаляется из памяти до тех пор, пока его фантомная link находится в этой очереди. Он будет удален только после того, How у фантомной ссылки будет вызван метод 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ов из памяти. Вот и все! :) На этом наше сегодняшнее занятие окончено. Но на одной теории далеко не уедешь, поэтому пора возвращаться к решению задач! :)
Teswirler
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION