CodeGym /Java Blog /Willekeurig /Fantoomreferenties in Java
John Squirrels
Niveau 41
San Francisco

Fantoomreferenties in Java

Gepubliceerd in de groep Willekeurig
Hoi! In de discussie van vandaag zullen we in detail praten over "fantoomreferenties" (PhantomReference) in Java. Wat voor referenties zijn dit? Waarom worden ze "fantoomreferenties" genoemd? Hoe worden ze gebruikt? Zoals u zich zult herinneren, heeft Java 4 soorten referenties:
  1. StrongReference (gewone referenties die we maken bij het maken van een object):

    Cat cat = new Cat()

    In dit voorbeeld is kat een sterke referentie.

  2. SoftReference (zachte referentie). We hadden een les over zulke verwijzingen.

  3. WeakReference (zwakke referentie). Hier werd ook een les over gegeven .

  4. PhantomReference (fantoomreferentie).

De laatste drie zijn generieke typen met typeparameters (bijvoorbeeld SoftReference<Integer> , WeakReference<MyClass> ). De klassen SoftReference , WeakReference en PhantomReference worden overgenomen van de klasse Reference . De belangrijkste methoden bij het werken met deze klassen zijn:
  • get() — retourneert het object waarnaar wordt verwezen;

  • clear() — verwijdert de verwijzing naar het object.

U kent deze methoden nog uit de lessen SoftReference en WeakReference . Het is belangrijk om te onthouden dat ze anders werken met verschillende soorten referenties. Vandaag gaan we niet in op de eerste drie soorten. In plaats daarvan zullen we het hebben over fantoomreferenties. We zullen de andere soorten referenties bespreken, maar alleen met betrekking tot hoe ze verschillen van fantoomreferenties. Laten we gaan! :) Om te beginnen, waarom hebben we überhaupt fantoomreferenties nodig? Zoals u weet, geeft de Garbage Collector (gc) het geheugen vrij dat wordt gebruikt door onnodige Java-objecten. De verzamelaar verwijdert een object in twee "doorgangen". In de eerste doorgang kijkt het alleen naar objecten en markeert het, indien nodig, ze als "onnodig" (wat betekent "te verwijderen"). Als definalize()methode is overschreven voor het object, wordt het aangeroepen. Of misschien heet het niet - het hangt er allemaal vanaf of je geluk hebt. U herinnert zich waarschijnlijk dat dat finalize()wispelturig is :) In de tweede doorgang van de vuilnisman wordt het object verwijderd en wordt het geheugen vrijgemaakt. Het onvoorspelbare gedrag van de vuilnisman levert ons een aantal problemen op. We weten niet precies wanneer de vuilnisman gaat draaien. We weten niet of de finalize()methode zal worden aangeroepen. Bovendien kan een sterke verwijzing naar een object worden gemaakt terwijl de finalize()methode ervan wordt uitgevoerd, in welk geval het object helemaal niet wordt verwijderd. Voor programma's die veel van het beschikbare geheugen vragen, kan dit gemakkelijk leiden tot een OutOfMemoryError. Dit alles dwingt ons om fantoomreferenties te gebruiken. Feit is dat dit het gedrag van de vuilnisman verandert. Als het object alleen fantoomreferenties heeft, dan:
  • de methode finalize() wordt aangeroepen (als deze wordt overschreven)

  • als er niets verandert nadat de methode finalize() is voltooid en het object nog steeds kan worden verwijderd, wordt de fantoomverwijzing naar het object in een speciale wachtrij geplaatst: ReferenceQueue .

Het belangrijkste om te begrijpen bij het werken met fantoomreferenties is dat het object niet uit het geheugen wordt verwijderd totdat de fantoomreferentie in deze wachtrij staat. Het wordt pas verwijderd nadat de methode clear() is aangeroepen op de fantoomreferentie. Laten we naar een voorbeeld kijken. Eerst maken we een testklasse die bepaalde gegevens opslaat.

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");
   }
}
Wanneer we objecten maken, geven we ze opzettelijk een flinke "belasting" (door 50 miljoen "x"-tekens aan elk object toe te voegen) om meer geheugen in beslag te nemen. Bovendien overschrijven we de methode finalize() om ervoor te zorgen dat deze wordt uitgevoerd. Vervolgens hebben we een klasse nodig die zal erven van PhantomReference . Waarom hebben we zo'n klas nodig? Het is allemaal eenvoudig. Hierdoor kunnen we extra logica toevoegen aan de methode clear() om te verifiëren dat de fantoomreferentie echt is gewist (wat betekent dat het object is verwijderd).

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();
   }
}
Vervolgens hebben we een aparte thread nodig die wacht tot de vuilnisman zijn werk doet, en fantoomlinks verschijnen in onze ReferenceQueue . Zodra zo'n referentie in de wachtrij terechtkomt, wordt daarop de methode cleanup() aangeroepen:

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();
   }
}
En tot slot hebben we de main() methode nodig, die we in een aparte Main class zullen plaatsen . In die methode maken we een TestClass- object, een fantoomverwijzing ernaar en een wachtrij voor fantoomreferenties. Daarna bellen we de vuilnisophaaldienst en kijken wat er gebeurt :)

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();
   }
}
Console-uitvoer:

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! 
Wat zien we hier? Alles is verlopen zoals we gepland hadden! De methode finalize() van ons object is overschreven en werd aangeroepen terwijl de vuilnisophaler actief was. Vervolgens werd de fantoomreferentie in de ReferenceQueue geplaatst . Daar werd de methode clear() aangeroepen (waarin we cleanup() hebben aangeroepen om uit te voeren naar de console). Ten slotte werd het object uit het geheugen verwijderd. Nu zie je precies hoe dit werkt :) Je hoeft natuurlijk niet alle theorie over fantoomreferenties uit je hoofd te leren. Maar het zou goed zijn als u zich in ieder geval de hoofdpunten herinnert. Ten eerste zijn dit de zwakste referenties van allemaal. Ze spelen alleen een rol als er geen andere verwijzingen naar het object meer zijn. De lijst met referenties die we hierboven hebben gegeven, is gesorteerd in aflopende volgorde van sterk naar zwak: StrongReference -> SoftReference -> WeakReference -> PhantomReference Een fantoomreferentie komt alleen in de strijd als er geen sterke, zachte of zwakke referenties naar ons object zijn: ) Ten tweede retourneert de methode get() altijd null voor een fantoomreferentie. Hier is een eenvoudig voorbeeld waarbij we drie verschillende soorten referenties maken voor drie verschillende soorten auto's:

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

   }
}
Console-uitvoer:

Sedan@4554617c
HybridAuto@74a14482 
null
De methode get() retourneerde volledig gewone objecten voor de zachte en zwakke referenties, maar retourneerde null voor de fantoomreferentie. Ten derde worden fantoomreferenties voornamelijk gebruikt in gecompliceerde procedures voor het wissen van objecten uit het geheugen. Dat is het! :) Dat besluit onze les vandaag. Maar met theorie alleen kom je niet ver, dus het is tijd om terug te keren naar het oplossen van taken! :)
Opmerkingen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION