CodeGym/Java блог/Случаен/Фантомни препратки в Java
John Squirrels
Ниво
San Francisco

Фантомни препратки в Java

Публикувано в групата
здрасти В днешната дискусия ще говорим подробно за „фантомни препратки“ (PhantomReference) в Java. Що за препратки са това? Защо се наричат ​​"фантомни препратки"? Как се използват? Както си спомняте, Java има 4 вида препратки:
  1. StrongReference (обикновени препратки, които създаваме при създаване на обект):

    Cat cat = new Cat()

    В този пример котката е силна препратка.

  2. SoftReference (мека препратка). Имахме урок за подобни препратки.

  3. WeakReference (слаба референция). Тук имаше и урок за тях .

  4. PhantomReference (фантомна справка).

Последните три са общи типове с параметри на типа (например SoftReference<Integer> , WeakReference<MyClass> ). Класовете SoftReference , WeakReference и PhantomReference са наследени от класа Reference . Най-важните методи при работа с тези класове са:
  • get() — връща посочения обект;

  • clear() — премахва препратката към обекта.

Помните тези методи от уроците по SoftReference и WeakReference . Важно е да запомните, че те работят по различен начин с различни видове препратки. Днес няма да се потопим в първите три вида. Вместо това ще говорим за фантомни препратки. Ще се докоснем до другите видове препратки, но само по отношение на това How се различават от фантомните препратки. Да тръгваме! :) Като начало, защо изобщо са ни нужни фантомни препратки? Както знаете, събирачът на отпадъци (gc) освобождава паметта, използвана от ненужни Java обекти. Колекторът изтрива обект в две "минавания". При първото преминаване той разглежда само обекти и, ако е необходимо, ги маркира като "ненужни" (което означава "за изтриване"). Акоfinalize()методът е заменен за обекта, той се извиква. Или може би не се нарича - всичко зависи само дали имате късмет. Вероятно си спомняте, че това finalize()е непостоянно :) При второто преминаване на събирача на отпадъци обектът се изтрива и паметта се освобождава. Непредсказуемото поведение на събирача на отпадъци ни създава редица проблеми. Не знаем точно кога събирачът на отпадъци ще започне да работи. Не знаем дали finalize()методът ще бъде извикан. Освен това може да се създаде силна препратка към обект, докато неговият finalize()метод се изпълнява, в който случай обектът изобщо няма да бъде изтрит. За програми, които имат големи изисквания към наличната памет, това може лесно да доведе до OutOfMemoryError. Всичко това ни кара да използваме фантомни препратки. Факт е, че това променя поведението на събирача на отпадъци. Ако обектът има само фантомни препратки, тогава:
  • методът му finalize() се извиква (ако е заменен)

  • ако нищо не се промени, след като методът finalize() приключи и обектът все още може да бъде изтрит, тогава фантомната препратка към обекта се поставя в специална опашка: 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("The finalize method has been called on the TestClass object");
   }
}
Когато създаваме обекти, ние умишлено ще им дадем голямо „натоварване“ (чрез добавяне на 50 мorона знака „x“ към всеки обект), за да заемат повече памет. В допълнение, ние заместваме метода 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("Cleaning up a phantom reference! Removing an object from memory!");
       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("The thread monitoring the queue has started!");
       Reference ref = null;

       // Wait until the references appear in the queue
       while ((ref = referenceQueue.poll()) == null) {

           try {
               Thread.sleep(50);
           }

           catch (InterruptedException e) {
               throw new RuntimeException("Thread " + getName() + " was interrupted!");
           }
       }

       // As soon as a phantom reference appears in the queue, clean it up
       ((MyPhantomReference) ref).cleanup();
   }
}
И накрая, имаме нужда от метода main() , който ще поставим в отделен главен клас. В този метод ще създадем обект TestClass , фантомна препратка към него и опашка за фантомни препратки. След това ще се обадим на боклукчия и ще видим Howво ще стане :)
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("Collecting garbage!");

       System.gc();
       Thread.sleep(300);

       System.out.println("ref = " + ref);

       Thread.sleep(5000);

       System.out.println("Collecting garbage!");

       System.gc();
   }
}
Конзолен изход:
ref = MyPhantomReference@4554617c
The thread monitoring the queue has started!
Collecting garbage!
The finalize method has been called on the TestClass object
ref = MyPhantomReference@4554617c
Collecting garbage!
Cleaning up a phantom reference!
Removing an object from memory!
Какво виждаме тук? Всичко се случи, Howто планирахме! Методът finalize() на нашия обект е отменен и беше извикан, докато събирачът на отпадъци работи. След това фантомната препратка беше поставена в ReferenceQueue . Докато беше там, неговият метод clear() беше извикан (в рамките на който извикахме cleanup() , за да изведем към конзолата). Накрая обектът беше изтрит от паметта. Сега виждате How точно работи това :) Разбира се, не е нужно да наизустявате цялата теория за фантомните препратки. Но би било добре, ако запомните поне основните точки. Първо, това са най-слабите референции от всички. Те влизат в действие само когато не са останали други препратки към обекта. Списъкът с референции, който дадохме по-горе, е сортиран в низходящ ред от най-силната към най-слабата: StrongReference -> SoftReference -> WeakReference -> PhantomReference Фантомна референция влиза в битката само когато няма силни, меки or слаби референции към нашия обект: ) Второ, методът get() винаги връща null за фантомна препратка. Ето един прост пример, в който създаваме три различни типа справки за три различни типа автомобor:
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() върна изцяло обикновени обекти за меките и слабите препратки, но върна нула за фантомната препратка. Трето, фантомните препратки се използват главно в сложни proceduresи за изтриване на обекти от паметта. Това е! :) Това приключва днешния ни урок. Но не можете да стигнете далеч само с теория, така че е време да се върнете към решаването на задачи! :)
Коментари
  • Популярен
  • Нов
  • Стар
Трябва да сте влезли, за да оставите коментар
Тази страница все още няма коментари