Il n'y a pas si longtemps, j'ai interviewé des candidats à la recherche d'un emploi d'ingénieur senior en développement Java. Je les interviewe souvent et leur demande : « Pouvez-vous me présenter quelques références faibles en Java ? » Si l'intervieweur dit : « Eh bien, est-ce lié au garbage collection ? », je serai fondamentalement satisfait, et je ne le ferai pas. Je ne m'attends pas à ce que la réponse soit la description d'un article qui va au fond des choses.
Cependant, contrairement aux attentes, j'ai été surpris de constater que parmi près de 20 candidats ayant en moyenne 5 ans d'expérience en développement et un parcours très instruit, seules deux personnes étaient au courant de l'existence de références faibles, mais une seule de ces deux personnes était vraiment au courant. Renseignez-vous à ce sujet. Au cours du processus d'entretien, j'ai également essayé de susciter certaines choses pour voir si quelqu'un dirait soudainement « Alors ça y est », mais le résultat a été très décevant. J'ai commencé à me demander pourquoi cette connaissance était si ignorée. Après tout, les références faibles sont une fonctionnalité très utile, et cette fonctionnalité a été introduite lors de la sortie de Java 1.2 il y a 7 ans.
Eh bien, je ne m'attends pas à ce que vous deveniez un expert des références faibles après avoir lu cet article, mais je pense que vous devriez au moins comprendre ce que sont les références faibles, comment les utiliser et dans quels scénarios elles sont utilisées. Puisqu'il s'agit de concepts inconnus, j'expliquerai brièvement les trois questions précédentes.
Référence solide
La référence forte est la référence que nous utilisons souvent, et elle s’écrit ainsi :
Copiez le code comme suit :
Tampon StringBuffer = new StringBuffer();
Ce qui précède crée un objet StringBuffer et stocke une référence (forte) à cet objet dans le tampon de variables. Oui, il s’agit d’une opération pédiatrique (s’il vous plaît, pardonnez-moi de dire cela). La chose la plus importante à propos d'une référence forte est qu'elle peut rendre la référence forte, ce qui détermine son interaction avec le garbage collector. Concrètement, si un objet est accessible via une chaîne de liens fortement référencés (Fortement accessible), il ne sera pas recyclé. C'est exactement ce dont vous avez besoin si vous ne souhaitez pas que l'objet avec lequel vous travaillez soit recyclé.
Mais les citations fortes sont si fortes
Dans un programme, il est assez rare de définir une classe comme étant non extensible. Bien sûr, cela peut être réalisé en marquant la classe comme finale. Ou cela peut être plus compliqué, qui consiste à renvoyer une interface (Interface) via une méthode d'usine qui contient un nombre inconnu d'implémentations spécifiques. Par exemple, nous souhaitons utiliser une classe appelée Widget, mais cette classe ne peut pas être héritée, donc de nouvelles fonctions ne peuvent pas être ajoutées.
Mais que devons-nous faire si nous voulons suivre des informations supplémentaires sur l’objet Widget ? Supposons que nous devions enregistrer le numéro de série de chaque objet, mais comme la classe Widget ne contient pas cet attribut et ne peut pas être étendue, nous ne pouvons pas ajouter cet attribut. En fait, il n’y a aucun problème. HashMap peut résoudre complètement les problèmes ci-dessus.
Copiez le code comme suit :
SerialNumberMap.put(widget, widgetSerialNumber);
Cela peut sembler correct à première vue, mais des références fortes aux objets widgets peuvent causer des problèmes. Nous pouvons être sûrs que lorsqu'un numéro de série de widget n'est plus nécessaire, nous devons supprimer l'entrée de la carte. Si nous ne le supprimons pas, cela peut provoquer des fuites de mémoire ou nous pouvons supprimer les widgets que nous utilisons lorsque nous les supprimons manuellement, ce qui peut entraîner la perte de données valides. En fait, ces problèmes sont très similaires. C'est un problème que les langages sans mécanismes de garbage collection rencontrent souvent lors de la gestion de la mémoire. Mais nous n'avons pas à nous soucier de ce problème, car nous utilisons le langage Java avec un mécanisme de garbage collection.
Un autre problème que les références fortes peuvent causer est la mise en cache, en particulier pour les fichiers volumineux comme les images. Supposons que vous ayez un programme qui doit traiter les images fournies par les utilisateurs. Une approche courante consiste à mettre en cache les données d'image, car le chargement d'images à partir du disque coûte très cher et, en même temps, nous voulons également éviter d'avoir deux copies des mêmes données d'image. en mémoire en même temps.
Le but de la mise en cache est de nous empêcher de charger à nouveau des fichiers inutiles. Vous constaterez rapidement que le cache contiendra toujours une référence aux données d’image en mémoire. L'utilisation de références fortes forcera les données d'image à rester en mémoire, ce qui vous oblige à décider quand les données d'image ne sont plus nécessaires et à les supprimer manuellement du cache afin qu'elles puissent être récupérées par le garbage collector. Vous êtes donc à nouveau obligé de faire ce que fait le ramasse-miettes et de décider manuellement quels objets nettoyer.
Faible référence
Une référence faible est simplement une référence qui n’est pas si forte pour conserver l’objet en mémoire. En utilisant WeakReference, le garbage collector vous aidera à décider quand l'objet référencé est recyclé et à supprimer l'objet de la mémoire. Créez une référence faible comme suit :
Copiez le code comme suit :
eakReference<Widget> lowWidget = new WeakReference<Widget>(widget);
Vous pouvez obtenir le véritable objet Widget en utilisant faibleWidget.get(). Étant donné que les références faibles ne peuvent pas empêcher le ramasse-miettes de les recycler, vous constaterez que (lorsqu'il n'y a pas de référence forte à l'objet widget), null est soudainement renvoyé lors de l'utilisation. obtenir.
Le moyen le plus simple de résoudre le problème ci-dessus de l'enregistrement du numéro de séquence du widget consiste à utiliser la classe WeakHashMap intégrée à Java. WeakHashMap est presque identique à HashMap, la seule différence est que ses clés (pas les valeurs !!!) sont référencées par WeakReference. Lorsqu'une clé WeakHashMap est marquée comme poubelle, l'entrée correspondant à cette clé sera automatiquement supprimée. Cela évite le problème ci-dessus de suppression manuelle des objets Widget inutiles. WeakHashMap peut être facilement converti en HashMap ou Map.
File d'attente de référence
Une fois qu'un objet de référence faible commence à renvoyer null, l'objet pointé par la référence faible est marqué comme déchet. Et cet objet de référence faible (pas l’objet vers lequel il pointe) ne sert à rien. Habituellement, un certain nettoyage doit être effectué à ce moment-là. Par exemple, WeakHashMap supprimera les entrées inutiles à ce moment-là pour éviter de stocker des références faibles sans signification qui grandissent indéfiniment.
Les files d'attente de références facilitent le suivi des références indésirables. Lorsque vous transmettez un objet ReferenceQueue lors de la construction de WeakReference, lorsque l'objet pointé par la référence est marqué comme déchet, l'objet de référence sera automatiquement ajouté à la file d'attente de référence. Ensuite, vous pouvez traiter la file d'attente de référence entrante à une période fixe, par exemple en effectuant un travail de nettoyage pour gérer ces objets de référence inutiles.
Quatre types de références
Il existe en fait quatre types de références avec des forces différentes en Java, de fortes à faibles, ce sont les références fortes, les références douces, les références faibles et les références virtuelles. La section ci-dessus présente les références fortes et les références faibles, et la section suivante décrit les deux références restantes, les références logicielles et les références virtuelles.
Référence logicielle
Une référence souple est fondamentalement la même chose qu'une référence faible, sauf qu'elle a une plus grande capacité à empêcher la période de récupération de place de recycler l'objet vers lequel elle pointe qu'une référence faible. Si un objet est accessible par une référence faible, l'objet sera détruit par le garbage collector lors du prochain cycle de collecte. Mais si la référence logicielle peut être atteinte, l'objet restera en mémoire plus longtemps. Le garbage collector ne récupérera les objets accessibles par ces références logicielles que lorsque la mémoire est insuffisante.
Étant donné que les objets accessibles par des références logicielles resteront en mémoire plus longtemps que les objets accessibles par des références faibles, nous pouvons utiliser cette fonctionnalité pour la mise en cache. De cette façon, vous pouvez économiser beaucoup de choses. Le garbage collector se souciera du type actuellement accessible et du degré de consommation de mémoire pour le traitement.
Référence fantôme
Contrairement aux références souples et aux références faibles, les objets pointés par les références virtuelles sont très fragiles et nous ne pouvons pas obtenir les objets vers lesquels ils pointent via la méthode get. Sa seule fonction est qu'après le recyclage de l'objet vers lequel il pointe, il est ajouté à la file d'attente de référence pour enregistrer que l'objet pointé par la référence a été détruit.
Lorsque l'objet pointé par une référence faible devient accessible par une référence faible, la référence faible est ajoutée à la file d'attente de références. Cette opération se produit avant que la destruction d’objet ou le garbage collection ne se produisent réellement. Théoriquement, l'objet qui est sur le point d'être recyclé peut être ressuscité dans une méthode de destructeur non conforme. Mais cette référence faible sera détruite. Une référence virtuelle est ajoutée à la file d'attente de références uniquement après que l'objet vers lequel elle pointe a été supprimé de la mémoire. Sa méthode get continue de renvoyer null pour empêcher l'objet presque détruit vers lequel elle pointe d'être ressuscité.
Il existe deux principaux scénarios d'utilisation des références virtuelles. Il permet de savoir exactement quand l'objet auquel il fait référence est supprimé de la mémoire. Et en fait, c’est le seul moyen en Java. Cela est particulièrement vrai lorsqu'il s'agit de fichiers volumineux tels que des images. Lorsque vous déterminez qu'un objet de données d'image doit être recyclé, vous pouvez utiliser des références virtuelles pour déterminer si l'objet est recyclé avant de continuer à charger l'image suivante. De cette façon, vous pouvez éviter autant que possible de terribles erreurs de débordement de mémoire.
Le deuxième point est que les références virtuelles peuvent éviter de nombreux problèmes lors de la destruction. La méthode finalize peut ressusciter des objets sur le point d'être détruits en créant des références fortes pointant vers eux. Cependant, un objet qui remplace la méthode finalize doit passer par deux cycles de récupération de place distincts s'il souhaite être recyclé. Lors du premier cycle, un objet est marqué comme recyclable et peut ensuite être détruit. Mais parce qu’il existe encore une légère possibilité que cet objet ressuscite lors du processus de destruction. Dans ce cas, le garbage collector doit être exécuté à nouveau avant que l'objet ne soit réellement détruit. Étant donné que la destruction peut ne pas être très rapide, un nombre indéterminé de cycles de récupération de place doivent être effectués avant que la destruction de l'objet ne soit appelée. Cela signifie qu’il peut y avoir un retard important dans le nettoyage de l’objet. C'est pourquoi vous obtenez toujours des erreurs de mémoire insuffisante lorsque la majeure partie du tas est marquée comme déchet.
En utilisant des références virtuelles, la situation ci-dessus sera résolue. Lorsqu'une référence virtuelle est ajoutée à la file d'attente de références, vous n'avez absolument aucun moyen d'obtenir un objet détruit. Car à ce moment-là, l'objet a été détruit de mémoire. Étant donné qu'une référence virtuelle ne peut pas être utilisée pour régénérer l'objet vers lequel elle pointe, son objet sera nettoyé lors du premier cycle de garbage collection.
Évidemment, il n’est pas recommandé de remplacer la méthode finalize. Étant donné que les références virtuelles sont évidemment sûres et efficaces, la suppression de la méthode finalize peut simplifier considérablement la machine virtuelle. Bien entendu, vous pouvez également remplacer cette méthode pour obtenir davantage. Tout dépend d'un choix personnel.
Résumer
Je pense qu'en voyant cela, beaucoup de gens commencent à se plaindre. Pourquoi parlez-vous d'une ancienne API des dix dernières années ? Eh bien, d'après mon expérience, de nombreux programmeurs Java ne connaissent pas très bien ces connaissances. -une compréhension approfondie est nécessaire, et j'espère que tout le monde pourra tirer quelque chose de cet article.