CodeGym /Java blog /Véletlen /Phantom hivatkozások Java nyelven
John Squirrels
Szint
San Francisco

Phantom hivatkozások Java nyelven

Megjelent a csoportban
Szia! A mai beszélgetésben részletesen beszélünk a Java „fantom referenciáiról” (PhantomReference). Milyen referenciák ezek? Miért hívják "fantom-hivatkozásnak"? Hogyan használják őket? Mint emlékszel, a Java 4 féle hivatkozással rendelkezik:
  1. StrongReference (közönséges hivatkozások, amelyeket egy objektum létrehozásakor hozunk létre):

    Cat cat = new Cat()

    Ebben a példában a macska egy erős hivatkozás.

  2. SoftReference (lágy referencia). Volt egy leckénk az ilyen hivatkozásokról.

  3. WeakReference (gyenge referencia). Volt itt egy lecke is róluk .

  4. PhantomReference (fantom referencia).

Az utolsó három általános típus típusparaméterekkel (például SoftReference<Integer> , WeakReference<MyClass> ). A SoftReference , WeakReference és PhantomReference osztályok a Reference osztályból öröklődnek . Az osztályokkal való munka során a legfontosabb módszerek a következők:
  • get() — a hivatkozott objektumot adja vissza;

  • clear() – eltávolítja az objektumra való hivatkozást.

Emlékszel ezekre a módszerekre a SoftReference és a WeakReference leckéiből . Fontos megjegyezni, hogy a különböző típusú hivatkozásokkal eltérően működnek. Ma nem az első három típusba merülünk bele. Ehelyett fantomreferenciákról fogunk beszélni. Kitérünk a többi hivatkozási típusra is, de csak abban a tekintetben, hogy miben különböznek a fantomhivatkozásoktól. Gyerünk! :) Először is, miért van szükségünk egyáltalán fantomreferenciákra? Mint tudják, a szemétgyűjtő (gc) felszabadítja a felesleges Java objektumok által használt memóriát. A gyűjtő két "menetben" töröl egy objektumot. Az első lépésben csak az objektumokat nézi, és szükség esetén „feleslegesnek” (értsd: „törölendőnek”) jelöli meg. Ha afinalize()metódus felül lett írva az objektumnál, akkor hívják. Vagy talán nem is hívják – minden csak attól függ, hogy szerencsés vagy. Valószínűleg emlékszel, hogy ez finalize()ingatag :) A szemétgyűjtő második lépésében az objektum törlődik és a memória felszabadul. A szemétszállító kiszámíthatatlan viselkedése számos problémát okoz számunkra. Nem tudjuk pontosan, hogy mikor indul el a szemétszállító. Nem tudjuk, hogy a finalize()metódus meghívásra kerül-e. Ráadásul a metódus végrehajtása közben erős hivatkozás hozható létre egy objektumra finalize(), amely esetben az objektum egyáltalán nem törlődik. Azoknál a programoknál, amelyek nagy igényeket támasztanak a rendelkezésre álló memóriával szemben, ez könnyen egy OutOfMemoryError. Mindez arra késztet bennünket, hogy fantomhivatkozásokat használjunk. Az tény, hogy ez megváltoztatja a szemétszállító viselkedését. Ha az objektumnak csak fantomhivatkozásai vannak, akkor:
  • a finalize() metódus meghívásra kerül (ha felül van írva)

  • Ha semmi sem változik, miután a finalize() metódus befejeződött, és az objektum továbbra is törölhető, akkor az objektumra mutató fantomhivatkozás egy speciális sorba kerül: ReferenceQueue .

A legfontosabb dolog, amit meg kell érteni a fantomhivatkozásokkal végzett munka során, hogy az objektum nem törlődik a memóriából, amíg a fantomhivatkozása nincs ebben a sorban. Csak a clear() metódus meghívása után törlődik a fantomhivatkozáson. Nézzünk egy példát. Először is létrehozunk egy tesztosztályt, amely valamilyen adatot fog tárolni.

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");
   }
}
Amikor objektumokat hozunk létre, szándékosan nagy "terhelést" adunk nekik (50 millió "x" karakter hozzáadásával minden objektumhoz), hogy több memóriát foglaljanak el. Ezen túlmenően felülírjuk a finalize() metódust, hogy lássuk, fut-e. Ezután szükségünk van egy osztályra, amely a PhantomReference- ből örökli . Miért van szükségünk egy ilyen osztályra? Minden egyértelmű. Ez lehetővé teszi számunkra, hogy további logikát adjunk a clear() metódushoz annak ellenőrzésére, hogy a fantomhivatkozás valóban törlődött-e (ami azt jelenti, hogy az objektumot törölték).

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();
   }
}
Ezután szükségünk van egy külön szálra, amely megvárja, amíg a szemétgyűjtő elvégzi a feladatát, és a fantomhivatkozások megjelennek a ReferenceQueue- unkban . Amint egy ilyen hivatkozás a sorba kerül, a cleanup() metódus meghívódik rajta:

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();
   }
}
És végül szükségünk van a main() metódusra, amit egy külön Main osztályba fogunk tenni . Ezzel a módszerrel létrehozunk egy TestClass objektumot, egy fantomhivatkozást rá, és egy sort a fantomhivatkozásokhoz. Utána felhívjuk a szemétszállítót és meglátjuk mi lesz :)

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();
   }
}
Konzol kimenet:

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! 
Mit látunk itt? Minden úgy történt, ahogy elterveztük! Objektumunk finalize() metódusa felül van írva, és a szemétgyűjtő futása közben hívták meg. Ezután a fantomhivatkozás a ReferenceQueue- ba került . Amíg ott volt, a clear() metódusa meghívásra került (amelyen belül a cleanup()-ot hívtuk meg , hogy a kimenetet a konzolra küldjük). Végül az objektumot törölték a memóriából. Most már látod, hogy ez pontosan hogyan működik :) Természetesen nem kell memorizálnod az összes elméletet a fantomhivatkozásokról. De jó lenne, ha legalább a főbb pontokra emlékezne. Először is, ezek a leggyengébb hivatkozások az összes közül. Csak akkor lépnek életbe, ha nem marad más hivatkozás az objektumra. A fent megadott hivatkozások listája a legerősebbtől a leggyengébbig csökkenő sorrendben van rendezve: StrongReference -> SoftReference -> WeakReference -> PhantomReference A fantomhivatkozás csak akkor lép be a csatába, ha nincs erős, lágy vagy gyenge hivatkozás az objektumra: ) Másodszor, a get() metódus mindig nullát ad vissza fantomhivatkozás esetén. Íme egy egyszerű példa, ahol három különböző típusú referenciát hozunk létre három különböző típusú autóhoz:

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());

   }
}
Konzol kimenet:

Sedan@4554617c
HybridAuto@74a14482 
null
A get() metódus teljesen közönséges objektumokat adott vissza a lágy és gyenge hivatkozásokhoz, de nullát a fantomhivatkozáshoz. Harmadszor, a fantomhivatkozásokat főként bonyolult eljárások során használják objektumok memóriából való törlésére. Ez az! :) Ezzel a mai leckénk is véget ért. Csakhogy az elmélettel nem lehet sokra menni, így ideje visszatérni a feladatok megoldásához! :)
Hozzászólások
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION