Salut! Dans la discussion d'aujourd'hui, nous parlerons en détail des "références fantômes" (PhantomReference) en Java. De quel type de références s'agit-il ? Pourquoi les appelle-t-on "références fantômes" ? Comment sont-ils utilisés ? Comme vous vous en souviendrez, Java a 4 types de références :
-
StrongReference (références ordinaires que nous créons lors de la création d'un objet) :
Cat cat = new Cat()
Dans cet exemple, chat est une référence forte.
-
SoftReference (référence logicielle). Nous avons eu une leçon sur ces références.
-
WeakReference (référence faible). Il y avait aussi une leçon à leur sujet ici .
-
PhantomReference (référence fantôme).
-
get() — renvoie l'objet référencé ;
- clear() — supprime la référence à l'objet.
finalize()
méthode a été remplacée pour l'objet, elle est appelée. Ou peut-être que ça ne s'appelle pas - tout dépend seulement si vous avez de la chance. Vous vous souvenez probablement que finalize()
c'est inconstant :) Lors de la deuxième passe du ramasse-miettes, l'objet est supprimé et la mémoire est libérée. Le comportement imprévisible du ramasse-miettes nous crée un certain nombre de problèmes. Nous ne savons pas exactement quand le ramasse-miettes commencera à fonctionner. Nous ne savons pas si la finalize()
méthode sera appelée. De plus, une référence forte à un objet peut être créée pendant finalize()
l'exécution de sa méthode, auquel cas l'objet ne sera pas supprimé du tout. Pour les programmes qui sollicitent beaucoup la mémoire disponible, cela peut facilement conduire à un fichier OutOfMemoryError
. Tout cela nous pousse à utiliser des références fantômes. Le fait est que cela modifie le comportement du ramasse-miettes. Si l'objet n'a que des références fantômes, alors :
-
sa méthode finalize() est appelée (si elle est surchargée)
-
si rien ne change une fois la méthode finalize() terminée et que l'objet peut encore être supprimé, la référence fantôme à l'objet est placée dans une file d'attente spéciale : 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");
}
}
Lorsque nous créons des objets, nous leur donnons intentionnellement une "charge" importante (en ajoutant 50 millions de caractères "x" à chaque objet) afin d'occuper plus de mémoire. De plus, nous redéfinissons la méthode finalize() pour voir qu'elle est exécutée. Ensuite, nous avons besoin d'une classe qui héritera de PhantomReference . Pourquoi avons-nous besoin d'une telle classe ? Tout est simple. Cela nous permettra d'ajouter une logique supplémentaire à la méthode clear() afin de vérifier que la référence fantôme est vraiment effacée (ce qui signifie que l'objet a été supprimé).
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();
}
}
Ensuite, nous avons besoin d'un thread séparé qui attendra que le ramasse-miettes fasse son travail, et des liens fantômes apparaîtront dans notre ReferenceQueue . Dès qu'une telle référence se retrouve dans la file d'attente, la méthode cleanup() est appelée dessus :
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();
}
}
Et enfin, nous avons besoin de la méthode main() , que nous placerons dans une classe Main séparée . Dans cette méthode, nous allons créer un objet TestClass , une référence fantôme à celui-ci et une file d'attente pour les références fantômes. Après cela, nous appellerons le ramasse-miettes et nous verrons ce qui se passe :)
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();
}
}
Sortie console :
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!
Que voyons-nous ici ? Tout s'est passé comme nous l'avions prévu ! La méthode finalize() de notre objet est remplacée et elle a été appelée pendant que le ramasse-miettes était en cours d'exécution. Ensuite, la référence fantôme a été placée dans la ReferenceQueue . Là-bas, sa méthode clear() a été appelée (dans laquelle nous avons appelé cleanup() afin de sortir sur la console). Enfin, l'objet a été supprimé de la mémoire. Vous voyez maintenant exactement comment cela fonctionne :) Bien sûr, vous n'avez pas besoin de mémoriser toute la théorie sur les références fantômes. Mais ce serait bien si vous vous souveniez au moins des points principaux. Premièrement, ce sont les références les plus faibles de toutes. Ils n'entrent en jeu que lorsqu'il ne reste aucune autre référence à l'objet. La liste des références que nous avons donnée ci-dessus est triée par ordre décroissant de la plus forte à la plus faible : StrongReference -> SoftReference -> WeakReference -> PhantomReference Une référence fantôme n'entre dans la bataille que lorsqu'il n'y a pas de références fortes, douces ou faibles à notre objet : ) Deuxièmement, la méthode get() renvoie toujours null pour une référence fantôme. Voici un exemple simple où nous créons trois types de références différents pour trois types de voitures différents :
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());
}
}
Sortie console :
Sedan@4554617c
HybridAuto@74a14482
null
La méthode get() a renvoyé des objets entièrement ordinaires pour les références logicielles et faibles, mais elle a renvoyé null pour la référence fantôme. Troisièmement, les références fantômes sont principalement utilisées dans les procédures compliquées de suppression d'objets de la mémoire. C'est ça! :) Cela conclut notre leçon d'aujourd'hui. Mais vous ne pouvez pas aller loin sur la seule théorie, il est donc temps de revenir à la résolution de tâches ! :)
GO TO FULL VERSION