Szia! A mai beszélgetésben részletesen beszélünk a Java „fantom referenciáiról” (PhantomReference). Milyen referenciák ezek? Miért hívják "fantom-hivatkozásnak"? Hogyan használják őket? Mint emlékszel, a Java 4 féle hivatkozással rendelkezik:
-
StrongReference (közönséges hivatkozások, amelyeket egy objektum létrehozásakor hozunk létre):
Cat cat = new Cat()
Ebben a példában a macska egy erős hivatkozás.
-
SoftReference (lágy referencia). Volt egy leckénk az ilyen hivatkozásokról.
-
WeakReference (gyenge referencia). Volt itt egy lecke is róluk .
-
PhantomReference (fantom referencia).
-
get() — a hivatkozott objektumot adja vissza;
- clear() – eltávolítja az objektumra való hivatkozást.
finalize()
metódus felül lett írva az objektumnál, akkor hívják. Vagy talán nem is hívják – minden csak attól függ, hogy szerencsés vagy. Valószínűleg emlékszel, hogy ez finalize()
ingatag :) A szemétgyűjtő második lépésében az objektum törlődik és a memória felszabadul. A szemétszállító kiszámíthatatlan viselkedése számos problémát okoz számunkra. Nem tudjuk pontosan, hogy mikor indul el a szemétszállító. Nem tudjuk, hogy a finalize()
metódus meghívásra kerül-e. Ráadásul a metódus végrehajtása közben erős hivatkozás hozható létre egy objektumra finalize()
, amely esetben az objektum egyáltalán nem törlődik. Azoknál a programoknál, amelyek nagy igényeket támasztanak a rendelkezésre álló memóriával szemben, ez könnyen egy OutOfMemoryError
. Mindez arra késztet bennünket, hogy fantomhivatkozásokat használjunk. Az tény, hogy ez megváltoztatja a szemétszállító viselkedését. Ha az objektumnak csak fantomhivatkozásai vannak, akkor:
-
a finalize() metódus meghívásra kerül (ha felül van írva)
-
Ha semmi sem változik, miután a finalize() metódus befejeződött, és az objektum továbbra is törölhető, akkor az objektumra mutató fantomhivatkozás egy speciális sorba kerül: 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");
}
}
Amikor objektumokat hozunk létre, szándékosan nagy "terhelést" adunk nekik (50 millió "x" karakter hozzáadásával minden objektumhoz), hogy több memóriát foglaljanak el. Ezen túlmenően felülírjuk a finalize() metódust, hogy lássuk, fut-e. Ezután szükségünk van egy osztályra, amely a PhantomReference- ből örökli . Miért van szükségünk egy ilyen osztályra? Minden egyértelmű. Ez lehetővé teszi számunkra, hogy további logikát adjunk a clear() metódushoz annak ellenőrzésére, hogy a fantomhivatkozás valóban törlődött-e (ami azt jelenti, hogy az objektumot törölték).
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();
}
}
Ezután szükségünk van egy külön szálra, amely megvárja, amíg a szemétgyűjtő elvégzi a feladatát, és a fantomhivatkozások megjelennek a ReferenceQueue- unkban . Amint egy ilyen hivatkozás a sorba kerül, a cleanup() metódus meghívódik rajta:
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();
}
}
És végül szükségünk van a main() metódusra, amit egy külön Main osztályba fogunk tenni . Ezzel a módszerrel létrehozunk egy TestClass objektumot, egy fantomhivatkozást rá, és egy sort a fantomhivatkozásokhoz. Utána felhívjuk a szemétszállítót és meglátjuk mi lesz :)
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();
}
}
Konzol kimenet:
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!
Mit látunk itt? Minden úgy történt, ahogy elterveztük! Objektumunk finalize() metódusa felül van írva, és a szemétgyűjtő futása közben hívták meg. Ezután a fantomhivatkozás a ReferenceQueue- ba került . Amíg ott volt, a clear() metódusa meghívásra került (amelyen belül a cleanup()-ot hívtuk meg , hogy a kimenetet a konzolra küldjük). Végül az objektumot törölték a memóriából. Most már látod, hogy ez pontosan hogyan működik :) Természetesen nem kell memorizálnod az összes elméletet a fantomhivatkozásokról. De jó lenne, ha legalább a főbb pontokra emlékezne. Először is, ezek a leggyengébb hivatkozások az összes közül. Csak akkor lépnek életbe, ha nem marad más hivatkozás az objektumra. A fent megadott hivatkozások listája a legerősebbtől a leggyengébbig csökkenő sorrendben van rendezve: StrongReference -> SoftReference -> WeakReference -> PhantomReference A fantomhivatkozás csak akkor lép be a csatába, ha nincs erős, lágy vagy gyenge hivatkozás az objektumra: ) Másodszor, a get() metódus mindig nullát ad vissza fantomhivatkozás esetén. Íme egy egyszerű példa, ahol három különböző típusú referenciát hozunk létre három különböző típusú autóhoz:
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());
}
}
Konzol kimenet:
Sedan@4554617c
HybridAuto@74a14482
null
A get() metódus teljesen közönséges objektumokat adott vissza a lágy és gyenge hivatkozásokhoz, de nullát a fantomhivatkozáshoz. Harmadszor, a fantomhivatkozásokat főként bonyolult eljárások során használják objektumok memóriából való törlésére. Ez az! :) Ezzel a mai leckénk is véget ért. Csakhogy az elmélettel nem lehet sokra menni, így ideje visszatérni a feladatok megoldásához! :)
GO TO FULL VERSION