CodeGym/Java blog/Tilfældig/Phantom References i Java
John Squirrels
Niveau
San Francisco

Phantom References i Java

Udgivet i gruppen
Hej! I dagens diskussion vil vi tale i detaljer om "fantomreferencer" (PhantomReference) i Java. Hvilken slags referencer er det? Hvorfor kaldes de "fantomreferencer"? Hvordan bruges de? Som du kan huske, har Java 4 slags referencer:
  1. StrongReference (almindelige referencer, som vi opretter, når vi opretter et objekt):

    Cat cat = new Cat()

    I dette eksempel er kat en stærk reference.

  2. SoftReference (blød reference). Vi havde en lektion om sådanne referencer.

  3. WeakReference (svag reference). Der var også en lektion om dem her .

  4. PhantomReference (fantomreference).

De sidste tre er generiske typer med typeparametre (f.eks. SoftReference<Integer> , WeakReference<MyClass> ). Klasserne SoftReference , WeakReference og PhantomReference er nedarvet fra Reference - klassen. De vigtigste metoder, når du arbejder med disse klasser, er:
  • get() — returnerer det refererede objekt;

  • clear() — fjerner referencen til objektet.

Du husker disse metoder fra lektionerne om SoftReference og WeakReference . Det er vigtigt at huske, at de arbejder forskelligt med forskellige slags referencer. I dag vil vi ikke dykke ned i de første tre typer. I stedet vil vi tale om fantomreferencer. Vi vil komme ind på de andre typer referencer, men kun med hensyn til, hvordan de adskiller sig fra fantomreferencer. Lad os gå! :) Til at begynde med, hvorfor har vi overhovedet brug for fantomreferencer? Som du ved, frigiver garbage collector (gc) den hukommelse, der bruges af unødvendige Java-objekter. Samleren sletter et objekt i to "pas". I den første omgang ser den kun på objekter og markerer dem om nødvendigt som "unødvendige" (hvilket betyder "skal slettes"). Hvisfinalize()metode er blevet tilsidesat for objektet, kaldes det. Eller måske hedder det ikke - det hele afhænger kun af, om du er heldig. Du husker sikkert, at det finalize()er vægelsindet :) I affaldssamlerens andet gennemløb bliver objektet slettet og hukommelsen frigivet. Skraldemandens uforudsigelige adfærd skaber en række problemer for os. Vi ved ikke præcis, hvornår skraldesamleren begynder at køre. Vi ved ikke, om finalize()metoden bliver kaldt. Plus, en stærk reference til et objekt kan oprettes, mens dets finalize()metode udføres, i hvilket tilfælde objektet slet ikke vil blive slettet. For programmer, der stiller store krav til tilgængelig hukommelse, kan det nemt føre til en OutOfMemoryError. Alt dette presser os til at bruge fantomreferencer. Faktum er, at dette ændrer affaldssamlerens adfærd. Hvis objektet kun har fantomreferencer, så:
  • dens finalize() metode kaldes (hvis den er tilsidesat)

  • hvis intet ændrer sig, når finalize() -metoden er færdig, og objektet stadig kan slettes, placeres fantomreferencen til objektet i en speciel kø: ReferenceQueue .

Det vigtigste at forstå, når man arbejder med fantomreferencer, er, at objektet ikke slettes fra hukommelsen, før dets fantomreference er i denne kø. Det vil kun blive slettet efter clear()- metoden er kaldt på fantomreferencen. Lad os se på et eksempel. Først opretter vi en testklasse, der gemmer en form for data.
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");
   }
}
Når vi opretter objekter, vil vi med vilje give dem en stor "belastning" (ved at tilføje 50 millioner "x" tegn til hvert objekt) for at optage mere hukommelse. Derudover tilsidesætter vi finalize()- metoden for at se, at den køres. Dernæst har vi brug for en klasse, der vil arve fra PhantomReference . Hvorfor har vi brug for sådan en klasse? Det hele er ligetil. Dette vil give os mulighed for at tilføje yderligere logik til clear()- metoden for at verificere, at fantomreferencen virkelig er ryddet (hvilket betyder, at objektet er blevet slettet).
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();
   }
}
Dernæst har vi brug for en separat tråd, der venter på, at skraldeopsamleren gør sit arbejde, og fantomlinks vises i vores ReferenceQueue . Så snart en sådan reference ender i køen, kaldes cleanup() metoden på den:
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();
   }
}
Og endelig har vi brug for main()- metoden, som vi vil sætte den i en separat Main- klasse. I den metode opretter vi et TestClass- objekt, en fantomreference til det og en kø for fantomreferencer. Derefter ringer vi til skraldemanden og ser hvad der sker :)
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();
   }
}
Konsoludgang:
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!
Hvad ser vi her? Alt skete som vi havde planlagt! Vores objekts finalize()- metode tilsidesættes, og den blev kaldt, mens garbage collector kørte. Dernæst blev fantomreferencen sat ind i ReferenceQueue . Mens den var der, blev dens clear() -metode kaldt (inden for hvilken vi kaldte cleanup() for at udlæse til konsollen). Endelig blev objektet slettet fra hukommelsen. Nu kan du se præcis, hvordan dette virker :) Selvfølgelig behøver du ikke at lære al teorien om fantomreferencer udenad. Men det ville være godt, hvis du i det mindste husker hovedpunkterne. For det første er disse de svageste referencer af alle. De kommer kun i spil, når der ikke er andre referencer til objektet tilbage. Listen over referencer, som vi gav ovenfor, er sorteret i faldende rækkefølge fra stærkeste til svageste: StrongReference -> SoftReference -> WeakReference -> PhantomReference En fantomreference går kun ind i kampen, når der ikke er nogen stærke, bløde eller svage referencer til vores objekt: ) For det andet returnerer metoden get() altid null for en fantomreference. Her er et simpelt eksempel, hvor vi opretter tre forskellige typer referencer til tre forskellige typer biler:
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());

   }
}
Konsoludgang:
Sedan@4554617c
HybridAuto@74a14482
null
Get () -metoden returnerede helt almindelige objekter for de bløde og svage referencer, men den returnerede null for fantomreferencen. For det tredje bruges fantomreferencer hovedsageligt i komplicerede procedurer til sletning af objekter fra hukommelsen. Det er det! :) Det afslutter vores lektion i dag. Men du kan ikke nå langt på teori alene, så det er tid til at vende tilbage til at løse opgaver! :)
Kommentarer
  • Populær
  • Ny
  • Gammel
Du skal være logget ind for at skrive en kommentar
Denne side har ingen kommentarer endnu