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:
-
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.
-
SoftReference (blød reference). Vi havde en lektion om sådanne referencer.
-
WeakReference (svag reference). Der var også en lektion om dem her .
-
PhantomReference (fantomreference).
-
get() — returnerer det refererede objekt;
- clear() — fjerner referencen til objektet.
finalize()
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 .
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! :)
GO TO FULL VERSION