CodeGym/Java-blogg/Tilfeldig/Fantomreferanser i Java
John Squirrels
Nivå
San Francisco

Fantomreferanser i Java

Publisert i gruppen
Hei! I dagens diskusjon skal vi snakke i detalj om "fantomreferanser" (PhantomReference) i Java. Hva slags referanser er dette? Hvorfor kalles de "fantomreferanser"? Hvordan brukes de? Som du husker har Java 4 typer referanser:
  1. StrongReference (vanlige referanser som vi lager når vi lager et objekt):

    Cat cat = new Cat()

    I dette eksemplet er katt en sterk referanse.

  2. SoftReference (myk referanse). Vi hadde en leksjon om slike referanser.

  3. WeakReference (svak referanse). Det var også en leksjon om dem her .

  4. PhantomReference (fantomreferanse).

De tre siste er generiske typer med typeparametere (for eksempel SoftReference<Integer> , WeakReference<MyClass> ). Klassene SoftReference , WeakReference og PhantomReference arves fra Reference- klassen. De viktigste metodene når du arbeider med disse klassene er:
  • get() — returnerer det refererte objektet;

  • clear() — fjerner referansen til objektet.

Du husker disse metodene fra leksjonene om SoftReference og WeakReference . Det er viktig å huske at de fungerer forskjellig med ulike typer referanser. I dag skal vi ikke dykke ned i de tre første typene. I stedet skal vi snakke om fantomreferanser. Vi vil berøre de andre typene referanser, men kun med hensyn til hvordan de skiller seg fra fantomreferanser. La oss gå! :) Til å begynne med, hvorfor trenger vi fantomreferanser i det hele tatt? Som du vet, frigjør søppelsamleren (gc) minnet som brukes av unødvendige Java-objekter. Samleren sletter et objekt i to "pass". I den første passeringen ser den kun på objekter, og om nødvendig markerer den som "unødvendig" (som betyr "skal slettes"). Hvisfinalize()metoden er overstyrt for objektet, kalles det. Eller kanskje det ikke heter - alt avhenger bare om du er heldig. Du husker sikkert at det finalize()er ustadig :) I søppelsamlerens andre pass blir objektet slettet og minnet frigjort. Søppelsamlerens uforutsigbare oppførsel skaper en rekke problemer for oss. Vi vet ikke nøyaktig når søppelsamleren begynner å gå. Vi vet ikke om finalize()metoden vil bli kalt. I tillegg kan en sterk referanse til et objekt opprettes mens finalize()metoden utføres, i så fall vil objektet ikke bli slettet i det hele tatt. For programmer som stiller store krav til tilgjengelig minne, kan dette lett føre til en OutOfMemoryError. Alt dette presser oss til å bruke fantomreferanser. Faktum er at dette endrer søppelsamlerens oppførsel. Hvis objektet bare har fantomreferanser, så:
  • dens finalize()- metode kalles (hvis den overstyres)

  • hvis ingenting endres når finalize()- metoden er ferdig og objektet fortsatt kan slettes, plasseres fantomreferansen til objektet i en spesiell kø: ReferenceQueue .

Det viktigste å forstå når du arbeider med fantomreferanser er at objektet ikke slettes fra minnet før fantomreferansen er i denne køen. Den vil bli slettet først etter at clear()- metoden kalles på fantomreferansen. La oss se på et eksempel. Først skal vi lage en testklasse som vil lagre en slags 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 lager objekter, vil vi med vilje gi dem en heftig "belastning" (ved å legge til 50 millioner "x"-tegn til hvert objekt) for å ta opp mer minne. I tillegg overstyrer vi finalize()- metoden for å se at den kjøres. Deretter trenger vi en klasse som vil arve fra PhantomReference . Hvorfor trenger vi en slik klasse? Det hele er enkelt. Dette vil tillate oss å legge til ytterligere logikk til clear()- metoden for å verifisere at fantomreferansen virkelig er slettet (som betyr at objektet er 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();
   }
}
Deretter trenger vi en egen tråd som venter på at søppelsamleren skal gjøre jobben sin, og fantomlenker vil vises i vår ReferenceQueue . Så snart en slik referanse havner i køen, kalles 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 til slutt trenger vi main()- metoden, som vi legger den i en egen hovedklasse . I den metoden vil vi lage et TestClass- objekt, en fantomreferanse til det og en kø for fantomreferanser. Etter det ringer vi søppelsamleren og ser hva som skjer :)
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();
   }
}
Konsoll utgang:
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!
Hva ser vi her? Alt skjedde som vi planla! Vårt objekts finalize()- metode er overstyrt og den ble kalt opp mens søppelsamleren kjørte. Deretter ble fantomreferansen satt inn i ReferenceQueue . Mens den var der, ble clear() -metoden kalt (innenfor kalte vi cleanup() for å sende ut til konsollen). Til slutt ble objektet slettet fra minnet. Nå ser du nøyaktig hvordan dette fungerer :) Du trenger selvfølgelig ikke å lære all teorien om fantomreferanser utenat. Men det ville vært bra om du husker i det minste hovedpoengene. For det første er dette de svakeste referansene av alle. De kommer bare inn når ingen andre referanser til objektet er igjen. Listen over referanser som vi ga ovenfor er sortert i synkende rekkefølge fra sterkest til svakeste: StrongReference -> SoftReference -> WeakReference -> PhantomReference En fantomreferanse kommer inn i kampen bare når det ikke er sterke, myke eller svake referanser til objektet vårt: ) For det andre returnerer get()- metoden alltid null for en fantomreferanse. Her er et enkelt eksempel hvor vi lager tre forskjellige typer referanser for tre forskjellige 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());

   }
}
Konsoll utgang:
Sedan@4554617c
HybridAuto@74a14482
null
Get () -metoden returnerte helt vanlige objekter for de myke og svake referansene, men den returnerte null for fantomreferansen. For det tredje brukes fantomreferanser hovedsakelig i kompliserte prosedyrer for å slette objekter fra minnet. Det er det! :) Det avslutter leksjonen vår i dag. Men du kommer ikke langt på teori alene, så det er på tide å gå tilbake til å løse oppgaver! :)
Kommentarer
  • Populær
  • Ny
  • Gammel
Du må være pålogget for å legge igjen en kommentar
Denne siden har ingen kommentarer ennå