JavaRush /Blog Java /Random-PL /PhantomReference w Javie

PhantomReference w Javie

Opublikowano w grupie Random-PL
Cześć! Na dzisiejszej lekcji omówimy szczegółowo referencje fantomowe w Javie. Co to za linki, dlaczego nazywa się je „fantomowymi” i jak z nich korzystać? Jak pamiętasz, w Javie istnieją 4 typy linków:
  1. StrongReference (regularne referencje, które tworzymy podczas tworzenia obiektu):

    Cat cat = new Cat()

    cat w tym przykładzie jest silnym odniesieniem.

  2. SoftReference (miękkie odniesienie). Mieliśmy wykład na temat tych powiązań.

  3. Słabe odniesienie (słabe odniesienie). Był też o nich wykład tutaj .

  4. PhantomReference (odniesienie fantomowe).

Wpisywane są obiekty trzech ostatnich typów (na przykład SoftReference<Integer> , WeakReference<MyClass> ). Klasy SoftReferencei WeakReferencedziedziczą PhantomReferencez klasy Reference. Najważniejsze metody pracy z tymi klasami to:
  • get()- zwraca obiekt, do którego odnosi się ten link;

  • clear()— usuwa odniesienie do obiektu.

Pamiętacie te metody z wykładów o SoftReferencei WeakReference. Należy pamiętać, że działają one inaczej w przypadku różnych typów linków. Dzisiaj nie będziemy szczegółowo omawiać pierwszych trzech typów, ale porozmawiamy o łączach fantomowych. Poruszymy także inne rodzaje linków, ale tylko w części, w której porozmawiamy o tym, czym różnią się od nich linki fantomowe. Iść! :) Zacznijmy od tego, dlaczego w ogóle potrzebujemy linków fantomowych. Jak wiadomo, moduł zbierający śmieci (Garbage Collector lub gc) jest odpowiedzialny za zwalnianie pamięci ze zbędnych obiektów Java . Kolekcjoner usuwa obiekt w dwóch „przejściach”. Przy pierwszym przejściu jedynie przegląda obiekty i w razie potrzeby oznacza je jako „niepotrzebne i do usunięcia”. Jeśli dla tego obiektu została przesłonięta metoda finalize(), jest ona wywoływana. Lub nie nazywa się to - w zależności od twojego szczęścia. Pewnie pamiętasz, że to finalize()kapryśna rzecz :) W drugim przebiegu kolektora obiekt zostaje usunięty, a pamięć zostaje zwolniona. To nieprzewidywalne zachowanie śmieciarza stwarza dla nas szereg problemów. Nie wiemy dokładnie, kiedy śmieciarka zacznie działać. Nie wiemy, czy metoda zostanie wywołana finalize(). Dodatkowo podczas operacji finalize()można utworzyć silne powiązanie z obiektem, a następnie w ogóle nie zostanie ono usunięte. W systemach wymagających dużej ilości pamięci może to łatwo doprowadzić do OutOfMemoryError. Wszystko to popycha nas w stronę korzystania z linków fantomowych . Chodzi o to, że zmienia to zachowanie modułu zbierającego śmieci. Jeżeli do obiektu pozostały tylko łącza fantomowe, to obiekt posiada:
  • metoda jest wywoływana finalize()(jeśli została przesłonięta);

  • jeżeli po pracy finalize()nic się nie zmieniło i obiekt nadal można usunąć, fantomowe odniesienie do obiektu umieszczane jest w specjalnej kolejce - ReferenceQueue.

Najważniejszą rzeczą do zrozumienia podczas pracy z odniesieniami fantomowymi jest to, że obiekt nie jest usuwany z pamięci, dopóki jego odniesienie fantomowe znajduje się w tej kolejce . Zostanie usunięty dopiero po utworzeniu linku fantomowego clear(). Spójrzmy na przykład. Najpierw utwórzmy klasę testową, która będzie przechowywać pewne dane.
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("У obiektа TestClass вызван метод finalize!!!");
   }
}
Celowo „ładowujemy” obiekty danymi podczas ich tworzenia (dodając 50 milionów znaków „x” do każdego obiektu), aby zająć więcej pamięci. Ponadto specjalnie zastępujemy metodę, finalize()aby sprawdzić, czy zadziałała. Następnie potrzebujemy klasy, która będzie dziedziczyć z PhantomReference. Dlaczego potrzebujemy takiej klasy? To proste. W ten sposób możemy dodać do metody dodatkową logikę, clear()aby sprawdzić, czy odniesienie fantomowe zostało faktycznie usunięte (a zatem obiekt został usunięty).
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("Очистка фантомной ссылки! Удаление obiektа из памяти!");
       clear();
   }
}
Następnie będziemy potrzebować osobnego wątku, który poczeka, aż moduł zbierający elementy bezużyteczne zrobi swoje, a ReferenceQueuew naszej kolejce pojawią się łącza fantomowe. Gdy tylko taki link trafi do kolejki, jego metoda zostanie wywołana 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() + " был прерван!");
           }
       }

       //Jak только в очереди появилась фантомная połączyć - очистить ее
       ((MyPhantomReference) ref).cleanup();
   }
}
I na koniec potrzebujemy metody main(): umieśćmy ją w osobnej klasie Main. Stworzymy w nim obiekt TestClass, fantomowe odniesienie do niego i kolejkę fantomowych odniesień. Potem zadzwonimy do śmieciarza i zobaczymy co się stanie :)
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();
   }
}
Dane wyjściowe konsoli: ref = MyPhantomReference@4554617c Wątek monitorujący kolejkę został uruchomiony! Wzywa się zbieranie śmieci! Na obiekcie TestClass została wywołana metoda finalize!!! ref = MyPhantomReference@4554617c Trwa zbieranie śmieci! Czyszczenie łącza fantomowego! Usuwanie obiektu z pamięci! Co tu widzimy? Wszystko odbyło się tak, jak planowaliśmy! W naszej klasie obiektów przesłonięto metodę finalize()i została ona wywołana, gdy moduł zbierający był uruchomiony. Następnie łącze fantomowe umieszczono w kolejce ReferenceQueue. Tam miała wywołaną metodę clear()(z której dodaliśmy cleanup()dane wyjściowe do konsoli). W rezultacie obiekt został usunięty z pamięci. Teraz widzisz dokładnie jak to działa :) Oczywiście nie musisz zapamiętywać całej teorii związanej z łączami fantomowymi. Ale będzie dobrze, jeśli pamiętasz przynajmniej główne punkty. Po pierwsze , są to najsłabsze ogniwa ze wszystkich. Wchodzą w grę tylko wtedy, gdy nie ma innych odniesień do przedmiotu. Lista linków, które podaliśmy powyżej, jest uporządkowana według malejącej siły: StrongReference-> SoftReference-> WeakReference-> PhantomReference Łącze fantomowe wejdzie do bitwy tylko wtedy, gdy nie ma silnych, miękkich lub słabych linków do naszego obiektu :) Po drugie , metoda get()dla łącza fantomowego zawsze powraca null. Oto prosty przykład, w którym tworzymy trzy różne typy linków dla trzech różnych typów samochodów:
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());

   }
}
Wyjście konsoli: Sedan@4554617c HybridAuto@74a14482 null Metoda get()zwróciła całkiem normalne obiekty dla łącza miękkiego i słabego, ale zwróciła nulldla łącza fantomowego. Po trzecie , głównym obszarem zastosowania odniesień fantomowych są złożone procedury usuwania obiektów z pamięci. To wszystko! :) Na tym kończymy naszą dzisiejszą lekcję. Jednak sama teoria nie zaprowadzi Cię daleko, więc czas wrócić do rozwiązywania problemów! :)
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION