JavaRush /Blog Java /Random-FR /Référence fantôme en Java

Référence fantôme en Java

Publié dans le groupe Random-FR
Bonjour! Dans la leçon d'aujourd'hui, nous parlerons en détail des références fantômes en Java. De quel type de liens s’agit-il, pourquoi sont-ils appelés « fantômes » et comment les utiliser ? Comme vous vous en souvenez, il existe 4 types de liens en Java :
  1. StrongReference (références régulières que nous créons lors de la création d'un objet) :

    Cat cat = new Cat()

    cat dans cet exemple est une référence forte.

  2. SoftReference (référence logicielle). Nous avons eu une conférence sur ces liens.

  3. WeakReference (référence faible). Il y avait aussi une conférence à leur sujet, ici .

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

Les objets des trois derniers types sont typés (par exemple, SoftReference<Integer> , WeakReference<MyClass> ). Classes SoftReferenceet WeakReferencehérite PhantomReferencede la classe Reference. Les méthodes les plus importantes lorsque vous travaillez avec ces classes sont :
  • get()- renvoie l'objet auquel ce lien fait référence ;

  • clear()— supprime une référence à un objet.

Vous vous souvenez de ces méthodes lors de conférences sur SoftReferenceet WeakReference. Il est important de se rappeler qu’ils fonctionnent différemment avec différents types de liens. Aujourd'hui, nous n'examinerons pas les trois premiers types en détail, mais parlerons des liens fantômes. Nous aborderons également d'autres types de liens, mais uniquement dans la partie où nous expliquerons en quoi les liens fantômes en diffèrent. Aller! :) Commençons par pourquoi nous avons besoin de liens fantômes en premier lieu. Comme vous le savez, le garbage collector (Garbage Collector ou gc) est chargé de libérer la mémoire des objets Java inutiles . Le collectionneur retire l'objet en deux "passes". Lors du premier passage, il examine uniquement les objets et, si nécessaire, les marque comme « inutiles et à supprimer ». Si cet objet avait une méthode surchargée finalize(), il est appelé. Ou ça ne s'appelle pas - selon votre chance. Vous vous souvenez probablement que c'est finalize()une chose inconstante :) Lors du deuxième passage du collecteur, l'objet est supprimé et la mémoire est libérée. Ce comportement imprévisible du garbage collector nous crée un certain nombre de problèmes. Nous ne savons pas exactement quand le garbage collector commencera à fonctionner. Nous ne savons pas si la méthode sera appelée finalize(). De plus, pendant le fonctionnement, finalize()un lien fort vers l'objet peut être créé, et celui-ci ne sera alors pas supprimé du tout. Sur les systèmes gourmands en mémoire, cela peut facilement conduire à OutOfMemoryError. Tout cela nous pousse à utiliser des liens fantômes . Le fait est que cela change le comportement du garbage collector. S'il ne reste que des liens fantômes vers l'objet, alors il a :
  • la méthode est appelée finalize()(si elle est remplacée) ;

  • si après le travail, finalize()rien n'a changé et que l'objet peut toujours être supprimé, une 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 l'on travaille avec des références fantômes est qu'un objet n'est pas supprimé de la mémoire tant que sa référence fantôme est dans cette file d'attente . Il ne sera supprimé qu'après le lien fantôme clear(). Regardons un exemple. Tout d’abord, créons une classe de test qui stockera certaines 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("У an object TestClass вызван метод finalize!!!");
   }
}
Nous « chargeons » délibérément les objets avec des données lors de leur création (en ajoutant 50 millions de caractères « x » à chaque objet) afin d'occuper plus de mémoire. De plus, nous remplaçons spécifiquement la méthode finalize()pour voir qu’elle fonctionne. Ensuite, nous avons besoin d'une classe qui héritera de PhantomReference. Pourquoi avons-nous besoin d’une telle classe ? C'est simple. De cette façon, nous pouvons ajouter une logique supplémentaire à la méthode clear()pour voir que la référence fantôme a effectivement été effacée (et donc 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("Очистка фантомной ссылки! Удаление an object из памяти!");
       clear();
   }
}
Ensuite, nous aurons besoin d'un thread séparé qui attendra que le ramasse-miettes fasse son travail, et ReferenceQueuedes liens fantômes apparaîtront dans notre file d'attente. Dès qu'un tel lien entre dans la file d'attente, sa méthode sera appelée cleanup():
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("Поток, отслеживающий очередь, стартовал!");
       Reference ref = null;

       //ждем, пока в очереди появятся ссылки
       while ((ref = referenceQueue.poll()) == null) {

           try {
               Thread.sleep(50);
           }

           catch (InterruptedException e) {
               throw new RuntimeException("Поток " + getName() + " был прерван!");
           }
       }

       //How только в очереди появилась фантомная link - очистить ее
       ((MyPhantomReference) ref).cleanup();
   }
}
Et enfin, nous avons besoin d'une méthode main(): mettons-la dans une classe séparée Main. Dans celui-ci, 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 garbage collector et 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("Вызывается сборка мусора!");

       System.gc();
       Thread.sleep(300);

       System.out.println("ref = " + ref);

       Thread.sleep(5000);

       System.out.println("Вызывается сборка мусора!");

       System.gc();
   }
}
Sortie de la console : ref = MyPhantomReference@4554617c Le thread surveillant la file d'attente a démarré ! La collecte des déchets est appelée ! La méthode finalize a été appelée sur l'objet TestClass !!! ref = MyPhantomReference@4554617c La collecte des déchets est appelée ! Nettoyage du lien fantôme ! Supprimer un objet de la mémoire ! Que voit-on ici ? Tout s'est passé comme nous l'avions prévu ! Notre classe d'objets avait une méthode remplacée finalize()et elle était appelée pendant l'exécution du collecteur. Ensuite, le lien fantôme a été mis en file d'attente ReferenceQueue. Là, elle a appelé une méthode clear()(à partir de laquelle nous avons cleanup()ajouté une sortie à la console). En conséquence, 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 associée aux liens fantômes. Mais ce serait bien si vous vous souveniez au moins des points principaux. Premièrement , ce sont les maillons les plus faibles de tous. Ils n'entrent en jeu que lorsqu'il n'y a pas d'autres références à l'objet. La liste des liens que nous avons donné ci-dessus est par ordre décroissant de force : StrongReference-> SoftReference-> WeakReference-> PhantomReference Le lien fantôme n'entrera en bataille que lorsqu'il n'y aura pas de lien Fort, Doux ou Faible vers notre objet :) Deuxièmement , la méthode get()car le lien fantôme renvoie toujours null. Voici un exemple simple dans lequel nous créons trois types de liens 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 de la console : Sedan@4554617c HybridAuto@74a14482 null La méthode get()a renvoyé des objets tout à fait normaux pour le lien symbolique et le lien faible, mais a renvoyé nullpour le lien fantôme. Troisièmement , le principal domaine d'utilisation des références fantômes concerne les procédures complexes de suppression d'objets de la mémoire. C'est tout! :) Ceci conclut notre leçon d'aujourd'hui. Mais la théorie seule ne vous mènera pas loin, il est donc temps de recommencer à résoudre des problèmes ! :)
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION