CodeGym/Blog Java/Random-FR/Références fantômes en Java
Auteur
Milan Vucic
Programming Tutor at Codementor.io

Références fantômes en Java

Publié dans le groupe Random-FR
membres
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 :
  1. 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.

  2. SoftReference (référence logicielle). Nous avons eu une leçon sur ces références.

  3. WeakReference (référence faible). Il y avait aussi une leçon à leur sujet ici .

  4. PhantomReference (référence fantôme).

Les trois derniers sont des types génériques avec des paramètres de type (par exemple, SoftReference<Integer> , WeakReference<MyClass> ). Les classes SoftReference , WeakReference et PhantomReference sont héritées de la classe Reference . Les méthodes les plus importantes lorsque vous travaillez avec ces classes sont :
  • get() — renvoie l'objet référencé ;

  • clear() — supprime la référence à l'objet.

Vous vous souvenez de ces méthodes dans les leçons sur SoftReference et WeakReference . Il est important de se rappeler qu'ils fonctionnent différemment avec différents types de références. Aujourd'hui, nous ne plongerons pas dans les trois premiers types. Au lieu de cela, nous parlerons de références fantômes. Nous aborderons les autres types de références, mais uniquement en ce qui concerne leur différence avec les références fantômes. Allons-y! :) Pour commencer, pourquoi avons-nous besoin de références fantômes ? Comme vous le savez, le garbage collector (gc) libère la mémoire utilisée par les objets Java inutiles. Le collecteur supprime un objet en deux "passes". Lors de la première passe, il ne regarde que les objets et, si nécessaire, les marque comme "inutiles" (c'est-à-dire "à supprimer"). Si lafinalize()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 .

La chose la plus importante à comprendre lorsque vous travaillez avec des références fantômes est que l'objet n'est pas supprimé de la mémoire tant que sa référence fantôme n'est pas dans cette file d'attente. Il ne sera supprimé qu'après l'appel de la méthode clear() sur la référence fantôme. Prenons un exemple. Tout d'abord, nous allons créer une classe de test qui stockera un certain type de données.
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 ! :)
Commentaires
  • Populaires
  • Nouveau
  • Anciennes
Tu dois être connecté(e) pour laisser un commentaire
Cette page ne comporte pas encore de commentaires