CodeGym/Blog Java/Random-PL/Phantom References w Javie
Autor
Milan Vucic
Programming Tutor at Codementor.io

Phantom References w Javie

Opublikowano w grupie Random-PL
Cześć! W dzisiejszej dyskusji szczegółowo omówimy „referencje fantomowe” (PhantomReference) w Javie. Co to za referencje? Dlaczego nazywa się je „fantomowymi referencjami”? Jak są używane? Jak pamiętasz, Java ma 4 rodzaje referencji:
  1. StrongReference (zwykłe referencje, które tworzymy podczas tworzenia obiektu):

    Cat cat = new Cat()

    W tym przykładzie kot jest silnym odniesieniem.

  2. SoftReference (miękkie odniesienie). Mieliśmy lekcję na temat takich odniesień.

  3. WeakReference (słaba referencja). Tutaj też była lekcja o nich .

  4. PhantomReference (odniesienie fantomowe).

Ostatnie trzy to typy ogólne z parametrami typu (na przykład SoftReference<Integer> , WeakReference<MyClass> ). Klasy SoftReference , WeakReference i PhantomReference dziedziczone z klasy Reference . Najważniejszymi metodami podczas pracy z tymi klasami są:
  • get() — zwraca obiekt, do którego się odwołuje;

  • clear() — usuwa referencję do obiektu.

Pamiętasz te metody z lekcji na temat SoftReference i WeakReference . Należy pamiętać, że działają one inaczej z różnymi rodzajami odniesień. Dzisiaj nie będziemy zagłębiać się w pierwsze trzy typy. Zamiast tego porozmawiamy o fantomowych odniesieniach. Dotkniemy innych rodzajów odniesień, ale tylko w odniesieniu do tego, czym różnią się one od odniesień fantomowych. Chodźmy! :) Zacznijmy od tego, dlaczego w ogóle potrzebujemy odniesień do fantomów? Jak wiesz, moduł wyrzucania elementów bezużytecznych (gc) zwalnia pamięć używaną przez niepotrzebne obiekty Javy. Kolekcjoner usuwa obiekt w dwóch „przejściach”. W pierwszym przebiegu sprawdza tylko obiekty iw razie potrzeby oznacza je jako „niepotrzebne” (co oznacza „do usunięcia”). jeślifinalize()metoda została nadpisana dla obiektu, nazywa się to. A może to się nie nazywa — wszystko zależy tylko od tego, czy masz szczęście. Prawdopodobnie pamiętasz, że finalize()jest to kapryśne :) W drugim przejściu modułu wyrzucania elementów bezużytecznych obiekt jest usuwany, a pamięć jest zwalniana. Nieprzewidywalne zachowanie śmieciarza stwarza dla nas szereg problemów. Nie wiemy dokładnie, kiedy moduł wyrzucania elementów bezużytecznych zacznie działać. Nie wiemy, czy finalize()metoda zostanie wywołana. Ponadto silne odniesienie do obiektu może zostać utworzone podczas finalize()wykonywania jego metody, w którym to przypadku obiekt w ogóle nie zostanie usunięty. W przypadku programów, które mają duże wymagania w stosunku do dostępnej pamięci, może to łatwo doprowadzić do błędu pliku OutOfMemoryError. Wszystko to popycha nas do korzystania z fantomowych odniesień. Faktem jest, że zmienia to zachowanie modułu wyrzucania elementów bezużytecznych. Jeśli obiekt ma tylko fantomowe odniesienia, to:
  • wywoływana jest jego metoda finalize() (jeśli jest nadpisana)

  • jeśli po zakończeniu metody finalize() nic się nie zmieni , a obiekt nadal może zostać usunięty, wówczas fantomowe odwołanie do obiektu jest umieszczane w specjalnej kolejce: ReferenceQueue .

Najważniejszą rzeczą do zrozumienia podczas pracy z referencjami fantomowymi jest to, że obiekt nie jest usuwany z pamięci, dopóki jego referencja fantomowa nie znajdzie się w tej kolejce. Zostanie usunięty dopiero po wywołaniu metody clear() na odwołaniu fantomowym. Spójrzmy na przykład. Najpierw utworzymy klasę testową, która będzie przechowywać jakieś 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("The finalize method has been called on the TestClass object");
   }
}
Kiedy tworzymy obiekty, celowo obciążamy je dużym „obciążeniem” (dodając 50 milionów znaków „x” do każdego obiektu), aby zajmowały więcej pamięci. Ponadto nadpisujemy metodę finalize() , aby sprawdzić, czy została uruchomiona. Następnie potrzebujemy klasy, która będzie dziedziczyć po PhantomReference . Po co nam taka klasa? To wszystko jest proste. To pozwoli nam dodać dodatkową logikę do metody clear() w celu sprawdzenia, czy odwołanie do fantomu rzeczywiście zostało wyczyszczone (co oznacza, że ​​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("Cleaning up a phantom reference! Removing an object from memory!");
       clear();
   }
}
Następnie potrzebujemy osobnego wątku, który będzie czekał, aż moduł wyrzucania elementów bezużytecznych wykona swoją pracę, a linki fantomowe pojawią się w naszej kolejce referencyjnej . Gdy tylko takie odwołanie znajdzie się w kolejce, wywoływana jest na nim metoda 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();
   }
}
I na koniec potrzebujemy metody main() , którą umieścimy w osobnej klasie Main . W tej metodzie utworzymy obiekt TestClass , fantomowe odwołanie do niego i kolejkę dla fantomowych odwołań. 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("Collecting garbage!");

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

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

       Thread.sleep(5000);

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

       System.gc();
   }
}
Wyjście konsoli:
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!
Co tu widzimy? Wszystko odbyło się tak, jak planowaliśmy! Metoda finalize() naszego obiektu została nadpisana i została wywołana podczas działania modułu wyrzucania elementów bezużytecznych. Następnie odwołanie fantomowe zostało umieszczone w ReferenceQueue . Tam została wywołana jego metoda clear() (w ramach której wywołaliśmy cleanup() w celu wyjścia do konsoli). Ostatecznie obiekt został usunięty z pamięci. Teraz dokładnie widzisz, jak to działa :) Oczywiście, nie musisz uczyć się na pamięć całej teorii dotyczącej odniesień fantomowych. Ale byłoby dobrze, gdybyś pamiętał przynajmniej główne punkty. Po pierwsze, są to najsłabsze referencje ze wszystkich. Wchodzą do gry tylko wtedy, gdy nie ma już innych odniesień do obiektu. Lista referencji, którą podaliśmy powyżej, jest posortowana malejąco od najsilniejszej do najsłabszej: StrongReference -> SoftReference -> WeakReference -> PhantomReference Fantomowe referencje wchodzą do bitwy tylko wtedy, gdy nie ma silnych, miękkich lub słabych referencji do naszego obiektu: ) Po drugie, metoda get() zawsze zwraca wartość null dla odniesienia fantomowego. Oto prosty przykład, w którym tworzymy trzy różne typy referencji 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łkowicie zwykłe obiekty dla miękkich i słabych referencji, ale zwróciła wartość null dla referencji fantomowej. Po trzecie, odniesienia fantomowe są wykorzystywane głównie w skomplikowanych procedurach usuwania obiektów z pamięci. Otóż ​​to! :) To kończy naszą dzisiejszą lekcję. Ale na samej teorii daleko nie zajdziesz, więc czas wrócić do rozwiązywania zadań! :)
Komentarze
  • Popularne
  • Najnowsze
  • Najstarsze
Musisz się zalogować, aby dodać komentarz
Ta strona nie ma jeszcze żadnych komentarzy