JavaRush /Java блог /Random UA /PhantomReference в Java

PhantomReference в Java

Стаття з групи Random UA
Вітання! На сьогоднішньому занятті ми докладно поговоримо про «фантомні посилання» (PhantomReference) у Java. Що за посилання такі, чому називаються «фантомними» і як ними користуватися? Як ти пам'ятаєш, у Java є 4 види посилань:
  1. StrongReference (звичайні посилання, які ми створюємо під час створення об'єкта):

    Cat cat = new Cat()

    cat у цьому прикладі - Strong-посилання.

  2. SoftReference (м'яке посилання). Ми мали лекцію про ці посилання.

  3. WeakReference (слабке посилання). Про них також була лекція, ось .

  4. PhantomReference (фантомне посилання).

Об'єкти трьох останніх видів типізовані (наприклад, SoftReference<Integer> , WeakReference<MyClass> ). Класи SoftReference, WeakReferenceі PhantomReferenceуспадковуються від класу Reference. Найбільш важливі методи під час роботи з цими класами:
  • get()- Повертає об'єкт, на який посилається це посилання;

  • clear()- Видаляє посилання на об'єкт.

Ці методи ти пам'ятаєш з лекцій про SoftReferenceі WeakReference. Важливо пам'ятати, що вони працюють по-різному із різними видами посилань. Ми сьогодні не будемо докладно розглядати перші три типи, а поговоримо про фантомні посилання. Інші види посилань ми теж торкнемося, але тільки в тій частині, де говоритимемо, чим фантомні посилання від них відрізняються. Поїхали! :) Почнемо з того, навіщо нам взагалі потрібні фантомні посилання. Як ти знаєш, звільненням пам'яті від непотрібних об'єктів Java займається збирач сміття (Garbage Collector або gc). Складальник видаляє об'єкт у два «проходи». У перший прохід він лише дивиться на об'єкти, і, якщо треба, позначає його як «непотрібний, що підлягає видаленню». Якщо цей об'єкт був перевизначений методfinalize(), він викликається. Або не викликається – як пощастить. Ти напевно пам'ятаєш, що finalize()штука непостійна :) У другий прохід збирача об'єкт видаляється, і пам'ять звільняється. Така непередбачувана поведінка збирача сміття створює для нас низку проблем. Ми не знаємо коли саме розпочнеться робота збирача сміття. Ми не знаємо чи буде викликаний метод finalize(). Плюс до всього, під час роботи finalize()може бути створена strong-посилання на об'єкт, і тоді його взагалі не буде видалено. У системах, вимогливих до обсягу вільної пам'яті, це може призвести до OutOfMemoryError. Все це підштовхує нас до використання фантомних посилань . Справа в тому, що це змінює поведінку збирача сміття. Якщо на об'єкт залишабося лише фантомні посилання, то у нього:
  • викликається метод finalize()(якщо він перевизначено);

  • якщо після роботи finalize()нічого не змінилося і об'єкт все ще може бути видалений, фантомне посилання на об'єкт міститься у спеціальну чергу - ReferenceQueue.

Найважливіше, що потрібно розуміти під час роботи з фантомними посиланнями, — об'єкт не видаляється з пам'яті доти, доки його фантомне посилання перебуває в цій черзі . Він буде видалений тільки після того, як у фантомного посилання буде викликаний метод clear(). Давай розглянемо приклад. Для початку створимо тестовий клас, який зберігатиме в собі якісь дані.
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("У об'єкта TestClass вызван метод finalize!!!");
   }
}
Ми спеціально добре «завантажуємо» об'єкти даними при створенні (додаємо в кожен об'єкт по 50 мільйонів символів «х»), щоб зайняти більше пам'яті. Крім того, ми спеціально перевизначаємо метод finalize(), щоб побачити, що він спрацював. Далі нам знадобиться клас, який успадковуватиметься від PhantomReference. Навіщо нам потрібний такий клас? Все просто. Так ми зможемо додати додаткову логіку до методу clear(), щоб побачити, що очищення фантомного посилання справді відбулося (а отже, об'єкт видалено).
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("Очистка фантомной ссылки! Удаление об'єкта из памяти!");
       clear();
   }
}
Далі нам знадобиться окремий потік, який чекатиме, поки збирач сміття зробить свою справу, і в нашій черзі ReferenceQueueз'являться фантомні посилання. Як тільки таке посилання потрапить у чергу, у неї буде викликаний метод 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() + " был прерван!");
           }
       }

       //як только в очереди появилась фантомная посилання - очистить ее
       ((MyPhantomReference) ref).cleanup();
   }
}
І, нарешті, нам знадобиться метод main(): винесемо його в окремий клас Main. У ньому ми створимо об'єкт 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 Потік, що відстежує чергу, стартував! Викликається складання сміття! Об'єкт TestClass має метод finalize!!! ref = MyPhantomReference@4554617c Викликається складання сміття! Очищення фантомного посилання! Видалення об'єкта з пам'яті! Що ми тут бачимо? Все сталося, як ми планували! У нашого класу об'єкта був перевизначений метод finalize()і він був викликаний під час роботи збирача. Далі, фантомне посилання було поміщено в чергу ReferenceQueue. Там у неї був викликаний метод clear()(з якого ми зробабоcleanup(), щоб додати вивід у консоль). У результаті об'єкт видалено з пам'яті. Тепер ти бачиш, як саме це працює :) Звичайно, тобі не треба зазубривати напам'ять всю пов'язану з фантомними посиланнями теорію. Але буде добре, якщо ти пам'ятатимеш хоча б головні моменти. По-перше , це найслабші посилання з усіх. Вони вступають у роботу тільки тоді, коли на об'єкт не залишилося жодних інших посилань. Список посилань, які ми привели вище, йде за «зменшенням сабо»: StrongReference-> SoftReference-> WeakReference-> PhantomReference Фантомне посилання вступить у бій тільки коли на наш об'єкт не буде ні 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()повернув цілком нормальні об'єкти для м'якого посилання та слабкого посилання, але повернув nullдля фантомної. По-третє , основна область використання фантомних посилань - складні процедури видалення об'єктів з пам'яті. От і все! :) На цьому наше сьогоднішнє заняття закінчено. Але на одній теорії далеко не поїдеш, тому настав час повертатися до вирішення завдань! :)
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ