JavaRush /Java Blog /Random-IT /Riferimento Phantom in Java

Riferimento Phantom in Java

Pubblicato nel gruppo Random-IT
Ciao! Nella lezione di oggi parleremo in dettaglio dei Phantom Reference in Java. Che tipo di collegamenti sono questi, perché si chiamano “fantasma” e come utilizzarli? Come ricorderete, ci sono 4 tipi di collegamenti in Java:
  1. StrongReference (riferimenti regolari che creiamo durante la creazione di un oggetto):

    Cat cat = new Cat()

    cat in questo esempio è un riferimento forte.

  2. SoftReference (riferimento software). Abbiamo tenuto una conferenza su questi collegamenti.

  3. WeakReference (riferimento debole). C'era anche una conferenza su di loro, qui .

  4. PhantomReference (riferimento fantasma).

Vengono tipizzati gli oggetti degli ultimi tre tipi (ad esempio, SoftReference<Integer> , WeakReference<MyClass> ). Classi SoftReferenceed WeakReferenceeredita PhantomReferencedalla classe Reference. I metodi più importanti quando si lavora con queste classi sono:
  • get()- restituisce l'oggetto a cui fa riferimento questo collegamento;

  • clear()— elimina un riferimento a un oggetto.

Ricordi questi metodi dalle lezioni su SoftReferencee WeakReference. È importante ricordare che funzionano in modo diverso con diversi tipi di collegamenti. Oggi non considereremo in dettaglio le prime tre tipologie, ma parleremo dei collegamenti fantasma. Toccheremo anche altri tipi di link, ma solo nella parte in cui parleremo di come i link fantasma differiscono da essi. Andare! :) Cominciamo innanzitutto dal motivo per cui abbiamo bisogno dei collegamenti fantasma. Come sai, il garbage collector (Garbage Collector o gc) è responsabile della liberazione della memoria da oggetti Java non necessari . Il raccoglitore rimuove l'oggetto in due "passaggi". Al primo passaggio esamina solo gli oggetti e, se necessario, li contrassegna come “non necessari e da eliminare”. Se questo oggetto ha un metodo sovrascritto finalize(), viene chiamato. Oppure non si chiama, a seconda della fortuna. Probabilmente ricorderai che questa finalize()è una cosa volubile :) Nel secondo passaggio del raccoglitore, l'oggetto viene cancellato e la memoria viene liberata. Questo comportamento imprevedibile del garbage collector ci crea una serie di problemi. Non sappiamo esattamente quando il garbage collector inizierà a funzionare. Non sappiamo se il metodo verrà chiamato finalize(). Inoltre, durante il funzionamento finalize()è possibile creare un forte collegamento con l'oggetto e quindi non verrà affatto eliminato. Sui sistemi affamati di memoria, questo può facilmente portare a OutOfMemoryError. Tutto questo ci spinge verso l'utilizzo di collegamenti fantasma . Il punto è che questo cambia il comportamento del garbage collector. Se sono rimasti solo collegamenti fantasma all'oggetto, allora ha:
  • viene chiamato il metodo finalize()(se è sovrascritto);

  • se dopo il lavoro finalize()non è cambiato nulla e l'oggetto può ancora essere eliminato, un riferimento fantasma all'oggetto viene inserito in una coda speciale - ReferenceQueue.

La cosa più importante da capire quando si lavora con i riferimenti fantasma è che un oggetto non viene rimosso dalla memoria finché il suo riferimento fantasma si trova in questa coda . Verrà eliminato solo dopo l'eliminazione del collegamento fantasma clear(). Diamo un'occhiata a un esempio. Innanzitutto, creiamo una classe di test che memorizzerà alcuni dati.

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("У an object TestClass вызван метод finalize!!!");
   }
}
"Carichiamo" deliberatamente gli oggetti con dati durante la loro creazione (aggiungendo 50 milioni di caratteri "x" a ciascun oggetto) per occupare più memoria. Inoltre, sovrascriviamo specificamente il metodo finalize()per verificare che funzioni. Successivamente abbiamo bisogno di una classe che erediterà da PhantomReference. Perché abbiamo bisogno di una lezione del genere? È semplice. In questo modo possiamo aggiungere ulteriore logica al metodo clear()per vedere che il riferimento fantasma è stato effettivamente cancellato (e quindi l'oggetto è stato cancellato).

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("Очистка фантомной ссылки! Удаление an object из памяти!");
       clear();
   }
}
Successivamente, avremo bisogno di un thread separato che attenderà che il garbage collector faccia il suo lavoro e ReferenceQueuenella nostra coda appariranno collegamenti fantasma. Non appena un collegamento di questo tipo entra in coda, il suo metodo verrà chiamato 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() + " был прерван!");
           }
       }

       //How только в очереди появилась фантомная link - очистить ее
       ((MyPhantomReference) ref).cleanup();
   }
}
E infine, abbiamo bisogno di un metodo main(): inseriamolo in una classe separata Main. In esso creeremo un oggetto TestClass, un riferimento fantasma ad esso e una coda per i riferimenti fantasma. Dopodiché chiameremo il garbage collector e vedremo cosa succede :)

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();
   }
}
Output della console: ref = MyPhantomReference@4554617c Il thread che monitora la coda è iniziato! La raccolta rifiuti si chiama! Il metodo finalize è stato chiamato sull'oggetto TestClass!!! ref = MyPhantomReference@4554617c È stata chiamata la raccolta dei rifiuti! Ripulire il collegamento fantasma! Rimuovere un oggetto dalla memoria! Cosa vediamo qui? Tutto è successo come avevamo previsto! La nostra classe di oggetti aveva un metodo sovrascritto finalize()ed è stato chiamato mentre il raccoglitore era in esecuzione. Successivamente, il collegamento fantasma è stato messo in coda ReferenceQueue. Lì aveva un metodo chiamato clear()(da cui abbiamo cleanup()aggiunto l'output alla console). Di conseguenza, l'oggetto è stato rimosso dalla memoria. Ora vedi esattamente come funziona :) Naturalmente non è necessario memorizzare tutta la teoria associata ai collegamenti fantasma. Ma andrà bene se ricordi almeno i punti principali. Innanzitutto , questi sono gli anelli più deboli di tutti. Entrano in gioco solo quando non ci sono altri riferimenti all'oggetto. L'elenco dei collegamenti che abbiamo fornito sopra è in ordine decrescente di forza: StrongReference-> SoftReference-> WeakReference-> PhantomReference Il collegamento fantasma entrerà in battaglia solo quando non ci sono collegamenti forti, morbidi o deboli al nostro oggetto :) In secondo luogo , il metodo get()per il collegamento fantasma restituisce sempre null. Ecco un semplice esempio in cui creiamo tre diversi tipi di collegamenti per tre diversi tipi di auto:

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

   }
}
Output della console: Sedan@4554617c HybridAuto@74a14482 null Il metodo get()ha restituito oggetti abbastanza normali per il collegamento soft e il collegamento debole, ma ha restituito oggetti nullper quello fantasma. In terzo luogo , l'area di utilizzo principale dei riferimenti fantasma sono le procedure complesse per rimuovere oggetti dalla memoria. È tutto! :) Questo conclude la nostra lezione di oggi. Ma la teoria da sola non ti porterà lontano, quindi è ora di tornare a risolvere i problemi! :)
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION