CodeGym /Java blogg /Slumpmässig /Fantomreferenser i Java
John Squirrels
Nivå
San Francisco

Fantomreferenser i Java

Publicerad i gruppen
Hej! I dagens diskussion kommer vi att prata i detalj om "fantomreferenser" (PhantomReference) i Java. Vad är det för referenser? Varför kallas de "fantomreferenser"? Hur används de? Som du kommer ihåg har Java 4 typer av referenser:
  1. StrongReference (vanliga referenser som vi skapar när vi skapar ett objekt):

    Cat cat = new Cat()

    I det här exemplet är katt en stark referens.

  2. SoftReference (mjuk referens). Vi hade en lektion om sådana referenser.

  3. WeakReference (svag referens). Det fanns också en lektion om dem här .

  4. PhantomReference (fantomreferens).

De tre sista är generiska typer med typparametrar (till exempel SoftReference<Integer> , WeakReference<MyClass> ) . Klasserna SoftReference , WeakReference och PhantomReference ärvs från klassen Reference . De viktigaste metoderna när du arbetar med dessa klasser är:
  • get() — returnerar det refererade objektet;

  • clear() — tar bort referensen till objektet.

Du kommer ihåg dessa metoder från lektionerna om SoftReference och WeakReference . Det är viktigt att komma ihåg att de fungerar olika med olika typer av referenser. Idag ska vi inte dyka in i de tre första typerna. Istället ska vi prata om fantomreferenser. Vi kommer att beröra de andra typerna av referenser, men endast med avseende på hur de skiljer sig från fantomreferenser. Nu går vi! :) Till att börja med, varför behöver vi fantomreferenser överhuvudtaget? Som du vet släpper garbage collector (gc) minnet som används av onödiga Java-objekt. Samlaren raderar ett objekt i två "pass". I det första passet tittar den bara på objekt och markerar dem vid behov som "onödiga" (vilket betyder "ska raderas"). Omfinalize()metod har åsidosatts för objektet, kallas den. Eller det kanske inte heter — allt beror bara på om du har tur. Du minns säkert att det finalize()är ombytligt :) I sopsamlarens andra pass raderas objektet och minnet frigörs. Sopsamlarens oförutsägbara beteende skapar en rad problem för oss. Exakt när sophämtaren börjar köra vet vi inte. Vi vet inte om finalize()metoden kommer att kallas. Dessutom kan en stark referens till ett objekt skapas medan dess finalize()metod exekveras, i vilket fall objektet inte kommer att tas bort alls. För program som ställer höga krav på tillgängligt minne kan detta lätt leda till en OutOfMemoryError. Allt detta driver oss att använda fantomreferenser. Faktum är att detta förändrar sopsamlarens beteende. Om objektet bara har fantomreferenser, då:
  • dess finalize() -metod anropas (om den åsidosätts)

  • om ingenting ändras när finalize() -metoden är klar och objektet fortfarande kan tas bort, placeras fantomreferensen till objektet i en speciell kö: ReferenceQueue .

Det viktigaste att förstå när man arbetar med fantomreferenser är att objektet inte raderas från minnet förrän dess fantomreferens finns i denna kö. Den kommer att raderas först efter att metoden clear() har anropats på fantomreferensen. Låt oss titta på ett exempel. Först skapar vi en testklass som kommer att lagra någon form av 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 skapar objekt kommer vi avsiktligt att ge dem en rejäl "belastning" (genom att lägga till 50 miljoner "x" tecken till varje objekt) för att ta upp mer minne. Dessutom åsidosätter vi finalize() -metoden för att se att den körs. Därefter behöver vi en klass som kommer att ärva från PhantomReference . Varför behöver vi en sådan klass? Det hela är okomplicerat. Detta gör att vi kan lägga till ytterligare logik till metoden clear() för att verifiera att fantomreferensen verkligen är rensad (vilket betyder att objektet har tagits bort).

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();
   }
}
Därefter behöver vi en separat tråd som väntar på att sopsamlaren ska göra sitt jobb, och fantomlänkar kommer att dyka upp i vår ReferenceQueue . Så snart en sådan referens hamnar i kön anropas 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();
   }
}
Och slutligen behöver vi metoden main() , som vi lägger den i en separat huvudklass . I den metoden skapar vi ett TestClass- objekt, en fantomreferens till det och en kö för fantomreferenser. Efter det ska vi ringa sophämtaren och se vad som händer :)

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();
   }
}
Konsolutgång:

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! 
Vad ser vi här? Allt hände som vi planerat! Vårt objekts finalize() -metod åsidosätts och den anropades medan sopsamlaren körde. Därefter placerades fantomreferensen i ReferenceQueue . Där anropades metoden clear() (inom vilken vi anropade cleanup() för att mata ut till konsolen). Slutligen raderades objektet från minnet. Nu ser du exakt hur det här fungerar :) Du behöver naturligtvis inte memorera all teori om fantomreferenser. Men det vore bra om du åtminstone minns huvudpunkterna. För det första är dessa de svagaste referenserna av alla. De kommer till spel endast när inga andra referenser till objektet finns kvar. Listan med referenser som vi gav ovan är sorterade i fallande ordning från starkaste till svagaste: StrongReference -> SoftReference -> WeakReference -> PhantomReference En fantomreferens går endast in i striden när det inte finns några starka, mjuka eller svaga referenser till vårt objekt: ) För det andra returnerar get() -metoden alltid null för en fantomreferens. Här är ett enkelt exempel där vi skapar tre olika typer av referenser för tre olika typer av bilar:

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

   }
}
Konsolutgång:

Sedan@4554617c
HybridAuto@74a14482 
null
Metoden get() returnerade helt vanliga objekt för de mjuka och svaga referenserna, men den returnerade null för fantomreferensen. För det tredje används fantomreferenser främst i komplicerade procedurer för att radera objekt från minnet. Det är allt! :) Det avslutar vår lektion idag. Men du kommer inte långt på teorin ensam, så det är dags att återgå till att lösa uppgifter! :)
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION