CodeGym /Blog Java /Aleatoriu /Referințe Phantom în Java
John Squirrels
Nivel
San Francisco

Referințe Phantom în Java

Publicat în grup
Bună! În discuția de astăzi, vom vorbi în detaliu despre „referințe fantomă” (PhantomReference) în Java. Ce fel de referințe sunt acestea? De ce sunt numite „referințe fantomă”? Cum se folosesc? După cum vă veți aminti, Java are 4 tipuri de referințe:
  1. StrongReference (referințe obișnuite pe care le creăm atunci când creăm un obiect):

    Cat cat = new Cat()

    În acest exemplu, pisica este o referință puternică.

  2. SoftReference (referință soft). Am avut o lecție despre astfel de referințe.

  3. WeakReference (referință slabă). Aici a fost și o lecție despre ei .

  4. PhantomReference (referință fantomă).

Ultimele trei sunt tipuri generice cu parametri de tip (de exemplu, SoftReference<Integer> , WeakReference<MyClass> ). Clasele SoftReference , WeakReference și PhantomReference sunt moștenite din clasa Reference . Cele mai importante metode atunci când lucrați cu aceste clase sunt:
  • get() — returnează obiectul referit;

  • clear() — elimină referința la obiect.

Vă amintiți aceste metode din lecțiile despre SoftReference și WeakReference . Este important să rețineți că acestea funcționează diferit cu diferite tipuri de referințe. Astăzi nu ne vom scufunda în primele trei tipuri. În schimb, vom vorbi despre referințe fantomă. Vom atinge celelalte tipuri de referințe, dar numai în ceea ce privește modul în care acestea diferă de referințele fantomă. Să mergem! :) Pentru început, de ce avem nevoie de referințe fantomă? După cum știți, garbage collector (gc) eliberează memoria folosită de obiectele Java inutile. Colectorul șterge un obiect în două „treceri”. În prima trecere, se uită doar la obiecte și, dacă este necesar, le marchează ca „inutile” (adică „de șters”). Dacăfinalize()metoda a fost suprascrisă pentru obiect, este numită. Sau poate că nu se numește - totul depinde doar dacă ești norocos. Probabil vă amintiți că finalize()este volubil :) În a doua trecere a colectorului de gunoi, obiectul este șters și memoria este eliberată. Comportamentul imprevizibil al gunoiului ne creează o serie de probleme. Nu știm exact când va începe să funcționeze colectorul de gunoi. Nu știm dacă finalize()metoda va fi apelată. În plus, o referință puternică la un obiect poate fi creată în timp ce finalize()metoda sa este în curs de executare, caz în care obiectul nu va fi șters deloc. Pentru programele care solicită mult memoria disponibilă, acest lucru poate duce cu ușurință la un OutOfMemoryError. Toate acestea ne împing să folosim referințe fantomă. Cert este că acest lucru schimbă comportamentul gunoiului. Dacă obiectul are doar referințe fantomă, atunci:
  • metoda sa finalize() este numită (dacă este suprascrisă)

  • dacă nimic nu se schimbă odată ce metoda finalize() este terminată și obiectul poate fi în continuare șters, atunci referința fantomă la obiect este plasată într-o coadă specială: ReferenceQueue .

Cel mai important lucru de înțeles când lucrați cu referințe fantomă este că obiectul nu este șters din memorie până când referința sa fantomă nu este în această coadă. Acesta va fi șters numai după ce metoda clear() este apelată pe referința fantomă. Să ne uităm la un exemplu. Mai întâi, vom crea o clasă de testare care va stoca un fel de date.

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");
   }
}
Când creăm obiecte, le vom oferi în mod intenționat o „încărcare” mare (prin adăugarea a 50 de milioane de caractere „x” la fiecare obiect) pentru a ocupa mai multă memorie. În plus, suprascriem metoda finalize() pentru a vedea că este rulată. În continuare, avem nevoie de o clasă care va moșteni de la PhantomReference . De ce avem nevoie de o astfel de clasă? Totul este simplu. Acest lucru ne va permite să adăugăm o logică suplimentară la metoda clear() pentru a verifica dacă referința fantomă este într-adevăr ștearsă (ceea ce înseamnă că obiectul a fost șters).

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();
   }
}
Apoi, avem nevoie de un fir separat care va aștepta ca colectorul de gunoi să-și facă treaba, iar linkurile fantomă vor apărea în ReferenceQueue . De îndată ce o astfel de referință ajunge în coadă, metoda cleanup() este apelată pe ea:

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, în sfârșit, avem nevoie de metoda main() , pe care o vom pune într-o clasă Main separată . În această metodă, vom crea un obiect TestClass , o referință fantomă la acesta și o coadă pentru referințe fantomă. După aceea, vom chema gunoiul și vom vedea ce se întâmplă :)

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();
   }
}
Ieșire din consolă:

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! 
Ce vedem aici? Totul s-a întâmplat așa cum am plănuit! Metoda finalize() a obiectului nostru este suprascrisă și a fost apelată în timp ce colectorul de gunoi rula. Apoi, referința fantomă a fost introdusă în ReferenceQueue . În timp ce era acolo, metoda sa clear() a fost apelată (în cadrul căreia am apelat cleanup() pentru a ieși pe consolă). În cele din urmă, obiectul a fost șters din memorie. Acum vedeți exact cum funcționează asta :) Desigur, nu trebuie să memorați toată teoria despre referințele fantomă. Dar ar fi bine să vă amintiți măcar de punctele principale. În primul rând, acestea sunt cele mai slabe referințe dintre toate. Ele intră în joc numai atunci când nu sunt lăsate alte referințe la obiect. Lista de referințe pe care am dat-o mai sus este sortată în ordine descrescătoare de la cea mai puternică la cea mai slabă: StrongReference -> SoftReference -> WeakReference -> PhantomReference O referință fantomă intră în luptă numai atunci când nu există referințe puternice, blânde sau slabe la obiectul nostru: ) În al doilea rând, metoda get() returnează întotdeauna null pentru o referință fantomă. Iată un exemplu simplu în care creăm trei tipuri diferite de referințe pentru trei tipuri diferite de mașini:

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

   }
}
Ieșire din consolă:

Sedan@4554617c
HybridAuto@74a14482 
null
Metoda get() a returnat obiecte complet obișnuite pentru referințele soft și slabe, dar a returnat null pentru referința fantomă. În al treilea rând, referințele fantomă sunt utilizate în principal în proceduri complicate de ștergere a obiectelor din memorie. Asta este! :) Asta se încheie lecția noastră de astăzi. Dar nu poți merge departe doar pe teorie, așa că este timpul să te întorci la rezolvarea sarcinilor! :)
Comentarii
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION