Fonctionnalités « cachées » du langage
Cet article couvre un sujet très ciblé, que la plupart des développeurs rencontrent extrêmement rarement dans la pratique (et peuvent même ne pas avoir connaissance de son existence).
Nous vous recommandons de sauter ce chapitre si vous venez de commencer à apprendre JavaScript.
Rappelant le concept de base du principe d'accessibilité du chapitre Garbage collection, on peut noter que le moteur JavaScript est garanti de conserver en mémoire les valeurs accessibles ou en cours d'utilisation.
Par exemple:
// la variable utilisateur contient une référence forte à l'objet let user = { nom : "John" } ; // écrasons la valeur de la variable utilisateur utilisateur = nul ; // la référence est perdue et l'objet sera supprimé de la mémoire
Ou un code similaire, mais légèrement plus compliqué, avec deux références fortes :
// la variable utilisateur contient une référence forte à l'objet let user = { nom : "John" } ; // copié la référence forte à l'objet dans la variable admin laissez admin = utilisateur ; // écrasons la valeur de la variable utilisateur utilisateur = nul ; // l'objet est toujours accessible via la variable admin
L'objet { name: "John" }
ne serait supprimé de la mémoire que s'il n'y avait pas de références fortes à celui-ci (si nous écrasions également la valeur de la variable admin
).
En JavaScript, il existe un concept appelé WeakRef
, qui se comporte légèrement différemment dans ce cas.
Termes : « Référence forte », « Référence faible »
Référence forte – est une référence à un objet ou à une valeur, qui empêche leur suppression par le ramasse-miettes. Ainsi, en gardant en mémoire l’objet ou la valeur vers laquelle il pointe.
Cela signifie que l'objet ou la valeur reste en mémoire et n'est pas collecté par le ramasse-miettes aussi longtemps qu'il existe des références fortes et actives à celui-ci.
En JavaScript, les références ordinaires aux objets sont des références fortes. Par exemple:
// la variable utilisateur contient une référence forte à cet objet let user = { nom : "John" } ;
Référence faible – est une référence à un objet ou à une valeur, qui ne les empêche pas d'être supprimés par le ramasse-miettes. Un objet ou une valeur peut être supprimé par le ramasse-miettes si les seules références restantes à cet objet sont des références faibles.
Attention
Avant d’entrer dans le vif du sujet, il convient de noter que l’utilisation correcte des structures évoquées dans cet article nécessite une réflexion très approfondie et qu’il est préférable de les éviter si possible.
WeakRef
– est un objet qui contient une référence faible à un autre objet, appelé target
ou referent
.
La particularité de WeakRef
est qu'il n'empêche pas le garbage collector de supprimer son objet référent. En d’autres termes, un objet WeakRef
ne maintient pas l’objet referent
en vie.
Prenons maintenant la variable user
comme « référent » et créons une référence faible à partir de celle-ci vers la variable admin
. Pour créer une référence faible, vous devez utiliser le constructeur WeakRef
, en passant l'objet cible (l'objet auquel vous souhaitez une référence faible).
Dans notre cas, il s'agit de la variable user
:
// la variable utilisateur contient une référence forte à l'objet let user = { nom : "John" } ; // la variable admin contient une faible référence à l'objet let admin = new WeakRef(utilisateur);
Le diagramme ci-dessous représente deux types de références : une référence forte utilisant la variable user
et une référence faible utilisant la variable admin
:
Puis, à un moment donné, nous arrêtons d'utiliser la variable user
– elle est écrasée, sort de la portée, etc., tout en conservant l'instance WeakRef
dans la variable admin
:
// écrasons la valeur de la variable utilisateur utilisateur = nul ;
Une faible référence à un objet ne suffit pas à le maintenir « vivant ». Lorsque les seules références restantes à un objet référent sont des références faibles, le garbage collector est libre de détruire cet objet et d'utiliser sa mémoire pour autre chose.
Cependant, jusqu'à ce que l'objet soit réellement détruit, la référence faible peut le renvoyer, même s'il n'existe plus de références fortes à cet objet. Autrement dit, notre objet devient une sorte de « chat de Schrödinger » – nous ne pouvons pas savoir avec certitude s'il est « vivant » ou « mort » :
À ce stade, pour obtenir l'objet de l'instance WeakRef
, nous utiliserons sa méthode deref()
.
La méthode deref()
renvoie l'objet référent vers lequel pointe WeakRef
, si l'objet est toujours en mémoire. Si l'objet a été supprimé par le garbage collector, alors la méthode deref()
retournera undefined
:
laissez ref = admin.deref(); si (réf) { // l'objet est toujours accessible : on peut effectuer toutes les manipulations avec lui } autre { // l'objet a été récupéré par le garbage collector }
WeakRef
est généralement utilisé pour créer des caches ou des tableaux associatifs qui stockent des objets gourmands en ressources. Cela permet d'éviter d'empêcher la collecte de ces objets par le garbage collector uniquement en raison de leur présence dans le cache ou dans le tableau associatif.
L'un des principaux exemples est une situation dans laquelle nous avons de nombreux objets image binaires (par exemple, représentés par ArrayBuffer
ou Blob
) et nous souhaitons associer un nom ou un chemin à chaque image. Les structures de données existantes ne sont pas tout à fait adaptées à ces objectifs :
Utiliser Map
pour créer des associations entre des noms et des images, ou vice versa, conservera les objets images en mémoire puisqu'ils sont présents dans la Map
sous forme de clés ou de valeurs.
WeakMap
n'est pas non plus éligible pour cet objectif : parce que les objets représentés comme des clés WeakMap
utilisent des références faibles et ne sont pas protégés contre la suppression par le ramasse-miettes.
Mais, dans cette situation, nous avons besoin d’une structure de données qui utiliserait des références faibles dans ses valeurs.
Pour cela, nous pouvons utiliser une collection Map
, dont les valeurs sont des instances WeakRef
faisant référence aux gros objets dont nous avons besoin. Par conséquent, nous ne conserverons pas ces objets volumineux et inutiles en mémoire plus longtemps qu’ils ne devraient l’être.
Sinon, c'est un moyen de récupérer l'objet image du cache s'il est toujours accessible. S'il a été récupéré, nous le régénérerons ou le téléchargerons à nouveau.
De cette façon, moins de mémoire est utilisée dans certaines situations.
Vous trouverez ci-dessous un extrait de code qui démontre la technique d'utilisation de WeakRef
.
En bref, nous utilisons une Map
avec des clés de chaîne et des objets WeakRef
comme valeurs. Si l'objet WeakRef
n'a pas été collecté par le garbage collector, nous le récupérons du cache. Sinon, nous le retéléchargeons à nouveau et le mettons dans le cache pour une éventuelle réutilisation ultérieure :
fonction fetchImg() { // fonction abstraite pour télécharger des images... } fonction faibleRefCache(fetchImg) { // (1) const imgCache = new Map(); // (2) return (imgName) => { // (3) const cachedImg = imgCache.get(imgName); // (4) if (cachedImg?.deref()) { // (5) return cachedImg?.deref(); } const newImg = fetchImg(imgName); // (6) imgCache.set(imgName, new WeakRef(newImg)); // (7) retourner newImg ; } ; } const getCachedImg = faibleRefCache(fetchImg);
Examinons en détail ce qui s'est passé ici :
weakRefCache
– est une fonction d'ordre supérieur qui prend une autre fonction, fetchImg
, comme argument. Dans cet exemple, nous pouvons négliger une description détaillée de la fonction fetchImg
, puisqu'il peut s'agir de n'importe quelle logique de téléchargement d'images.
imgCache
– est un cache d'images, qui stocke les résultats mis en cache de la fonction fetchImg
, sous la forme de clés de chaîne (nom de l'image) et d'objets WeakRef
comme valeurs.
Renvoie une fonction anonyme qui prend le nom de l'image comme argument. Cet argument sera utilisé comme clé pour l'image mise en cache.
Essayer d'obtenir le résultat mis en cache à partir du cache, en utilisant la clé fournie (nom de l'image).
Si le cache contient une valeur pour la clé spécifiée et que l'objet WeakRef
n'a pas été supprimé par le garbage collector, renvoyez le résultat mis en cache.
S'il n'y a aucune entrée dans le cache avec la clé demandée, ou si la méthode deref()
renvoie undefined
(ce qui signifie que l'objet WeakRef
a été récupéré), la fonction fetchImg
télécharge à nouveau l'image.
Mettez l'image téléchargée dans le cache en tant qu'objet WeakRef
.
Nous avons maintenant une collection Map
, où les clés – sont des noms d’images sous forme de chaînes, et les valeurs – sont des objets WeakRef
contenant les images elles-mêmes.
Cette technique permet d'éviter d'allouer une grande quantité de mémoire à des objets gourmands en ressources, que plus personne n'utilise. Cela permet également d'économiser de la mémoire et du temps en cas de réutilisation d'objets mis en cache.
Voici une représentation visuelle de ce à quoi ressemble ce code :
Mais cette implémentation a ses inconvénients : au fil du temps, Map
sera rempli de chaînes comme clés, qui pointent vers un WeakRef
, dont l'objet référent a déjà été récupéré :
Une façon de résoudre ce problème consiste à vider périodiquement le cache et à effacer les entrées « mortes ». Une autre façon consiste à utiliser des finaliseurs, que nous explorerons ensuite.
Un autre cas d'utilisation de WeakRef
est le suivi des objets DOM.
Imaginons un scénario dans lequel un code ou une bibliothèque tierce interagit avec des éléments de notre page tant qu'ils existent dans le DOM. Par exemple, il peut s'agir d'un utilitaire externe permettant de surveiller et de notifier l'état du système (communément appelé « logger » – un programme qui envoie des messages d'information appelés « journaux »).
Exemple interactif :
Résultat
index.js
index.css
index.html
const startMessagesBtn = document.querySelector('.start-messages'); // (1) const closeWindowBtn = document.querySelector('.window__button'); // (2) const windowElementRef = new WeakRef(document.querySelector(".window__body")); // (3) startMessagesBtn.addEventListener('clic', () => { // (4) startMessages(windowElementRef); startMessagesBtn.disabled = true ; }); closeWindowBtn.addEventListener('click', () => document.querySelector(".window__body").remove()); // (5) const startMessages = (élément) => { const timerId = setInterval(() => { // (6) if (element.deref()) { // (7) const payload = document.createElement("p"); payload.textContent = `Message : État du système OK : ${new Date().toLocaleTimeString()}`; element.deref().append(charge utile); } autre { // (8) alert("L'élément a été supprimé."); // (9) clearInterval(timerId); } }, 1000); } ;
.app { affichage : flexible ; direction flexible : colonne ; écart : 16 px ; } .start-messages { largeur : ajustement du contenu ; } .fenêtre { largeur : 100 % ; bordure : 2px solide #464154 ; débordement : caché ; } .window__header { position : collante ; remplissage : 8 px ; affichage : flexible ; justifier-contenu : espace entre les deux ; aligner les éléments : centre ; couleur d'arrière-plan : #736e7e ; } .window__titre { marge : 0 ; taille de police : 24 px ; poids de la police : 700 ; couleur : blanc ; espacement des lettres : 1 px ; } .window__bouton { remplissage : 4 px ; arrière-plan : #4f495c ; contour : aucun ; bordure : 2px solide #464154 ; couleur : blanc ; taille de police : 16 px ; curseur : pointeur ; } .window__body { hauteur : 250 px ; remplissage : 16 px ; débordement : défilement ; couleur d'arrière-plan : #736e7e33 ; }
<!DOCTYPE HTML> <html lang="fr"> <tête> <méta charset="utf-8"> <link rel="stylesheet" href="index.css"> <title>Enregistreur DOM WeakRef</title> </tête> <corps> <div class="app"> <button class="start-messages">Commencer à envoyer des messages</button> <div class="fenêtre"> <div class="window__header"> <p class="window__title">Messages :</p> <button class="window__button">Fermer</button> </div> <div class="window__body"> Aucun message. </div> </div> </div> <script type="module" src="index.js"></script> </corps> </html>
Lorsque vous cliquez sur le bouton « Commencer à envoyer des messages », dans la « fenêtre d'affichage des journaux » (un élément avec la classe .window__body
), des messages (logs) commencent à apparaître.
Mais dès que cet élément est supprimé du DOM, le logger devrait cesser d’envoyer des messages. Pour reproduire la suppression de cet élément, il suffit de cliquer sur le bouton « Fermer » dans le coin supérieur droit.
Afin de ne pas compliquer notre travail, et de ne pas notifier le code tiers à chaque fois que notre élément DOM est disponible, et lorsqu'il ne l'est pas, il suffira de créer une référence faible à celui-ci en utilisant WeakRef
.
Une fois l'élément supprimé du DOM, l'enregistreur le remarquera et cessera d'envoyer des messages.
Examinons maintenant de plus près le code source ( onglet index.js
) :
Obtenez l'élément DOM du bouton « Commencer à envoyer des messages ».
Obtenez l'élément DOM du bouton « Fermer ».
Obtenez l'élément DOM de la fenêtre d'affichage des journaux à l'aide du new WeakRef()
. De cette façon, la variable windowElementRef
contient une faible référence à l'élément DOM.
Ajoutez un écouteur d'événement sur le bouton « Commencer à envoyer des messages », chargé de démarrer l'enregistreur lorsque vous cliquez dessus.
Ajoutez un écouteur d'événement sur le bouton « Fermer », chargé de fermer la fenêtre d'affichage des journaux lorsque vous cliquez dessus.
Utilisez setInterval
pour commencer à afficher un nouveau message chaque seconde.
Si l'élément DOM de la fenêtre d'affichage des journaux est toujours accessible et conservé en mémoire, créez et envoyez un nouveau message.
Si la méthode deref()
renvoie undefined
, cela signifie que l'élément DOM a été supprimé de la mémoire. Dans ce cas, l'enregistreur cesse d'afficher les messages et efface le minuteur.
alert
, qui sera appelé après que l'élément DOM de la fenêtre d'affichage des journaux soit supprimé de la mémoire (c'est-à-dire après avoir cliqué sur le bouton « Fermer »). Notez que la suppression de la mémoire peut ne pas se produire immédiatement, car elle dépend uniquement des mécanismes internes du garbage collector.
Nous ne pouvons pas contrôler ce processus directement à partir du code. Cependant, malgré cela, nous avons toujours la possibilité de forcer le garbage collection depuis le navigateur.
Dans Google Chrome par exemple, pour cela, vous devez ouvrir les outils de développement ( Ctrl + Shift + J sous Windows/Linux ou Option + ⌘ + J sous macOS), aller dans l'onglet « Performances », et cliquer sur l'icône Bouton icône poubelle – « Collecter les déchets » :
Cette fonctionnalité est prise en charge dans la plupart des navigateurs modernes. Une fois les actions entreprises, l’ alert
se déclenchera immédiatement.
Il est maintenant temps de parler des finaliseurs. Avant de continuer, clarifions la terminologie :
Rappel de nettoyage (finaliseur) – est une fonction qui est exécutée lorsqu'un objet, enregistré dans FinalizationRegistry
, est supprimé de la mémoire par le ramasse-miettes.
Son objectif est de fournir la possibilité d'effectuer des opérations supplémentaires liées à l'objet après sa suppression définitive de la mémoire.
Registry (ou FinalizationRegistry
) – est un objet spécial en JavaScript qui gère l'enregistrement et la désinscription des objets et leurs rappels de nettoyage.
Ce mécanisme permet d'enregistrer un objet pour le suivre et lui associer un rappel de nettoyage. Il s'agit essentiellement d'une structure qui stocke des informations sur les objets enregistrés et leurs rappels de nettoyage, puis appelle automatiquement ces rappels lorsque les objets sont supprimés de la mémoire.
Pour créer une instance de FinalizationRegistry
, il doit appeler son constructeur, qui prend un seul argument : le rappel de nettoyage (finaliseur).
Syntaxe:
fonction cleanupCallback (heldValue) { // code de rappel de nettoyage } const registre = nouveau FinalizationRegistry (cleanupCallback);
Ici:
cleanupCallback
– un rappel de nettoyage qui sera automatiquement appelé lorsqu'un objet enregistré est supprimé de la mémoire.
heldValue
– la valeur transmise comme argument au rappel de nettoyage. Si heldValue
est un objet, le registre y conserve une référence forte.
registry
– une instance de FinalizationRegistry
.
Méthodes FinalizationRegistry
:
register(target, heldValue [, unregisterToken])
– utilisé pour enregistrer des objets dans le registre.
target
– l’objet enregistré pour le suivi. Si la target
est récupérée, le rappel de nettoyage sera appelé avec heldValue
comme argument.
unregisterToken
facultatif – un jeton de désinscription. Il peut être transmis pour désenregistrer un objet avant que le garbage collector ne le supprime. En règle générale, l'objet target
est utilisé comme unregisterToken
, ce qui constitue la pratique standard.
unregister(unregisterToken)
– la méthode unregister
est utilisée pour désenregistrer un objet du registre. Il faut un argument – unregisterToken
(le jeton de désinscription obtenu lors de l’enregistrement de l’objet).
Passons maintenant à un exemple simple. Utilisons l'objet user
déjà connu et créons une instance de FinalizationRegistry
:
let user = { nom : "John" } ; const registre = nouveau FinalizationRegistry ((heldValue) => { console.log(`${heldValue} a été collecté par le ramasse-miettes.`); });
Ensuite, nous enregistrerons l'objet, qui nécessite un rappel de nettoyage en appelant la méthode register
:
registre.register(utilisateur, utilisateur.nom);
Le registre ne conserve pas de référence forte à l’objet enregistré, car cela irait à l’encontre de son objectif. Si le registre conservait une référence forte, l'objet ne serait jamais récupéré.
Si l'objet est supprimé par le ramasse-miettes, notre rappel de nettoyage peut être appelé à un moment donné dans le futur, avec la heldValue
qui lui est transmise :
// Lorsque l'objet utilisateur est supprimé par le garbage collector, le message suivant sera imprimé dans la console : "John a été récupéré par le éboueur."
Il existe également des situations dans lesquelles, même dans les implémentations utilisant un rappel de nettoyage, il est possible qu'il ne soit pas appelé.
Par exemple:
Lorsque le programme termine complètement son fonctionnement (par exemple, lors de la fermeture d'un onglet dans un navigateur).
Lorsque l'instance FinalizationRegistry
elle-même n'est plus accessible au code JavaScript. Si l'objet qui crée l'instance FinalizationRegistry
sort de la portée ou est supprimé, les rappels de nettoyage enregistrés dans ce registre peuvent également ne pas être appelés.
En revenant à notre exemple de cache faible , nous pouvons remarquer ce qui suit :
Même si les valeurs enveloppées dans WeakRef
ont été collectées par le garbage collector, il existe toujours un problème de « fuite de mémoire » sous la forme des clés restantes, dont les valeurs ont été collectées par le garbage collector.
Voici un exemple de mise en cache amélioré utilisant FinalizationRegistry
:
fonction fetchImg() { // fonction abstraite pour télécharger des images... } fonction faibleRefCache (fetchImg) { const imgCache = new Map(); const registre = nouveau FinalizationRegistry((imgName) => { // (1) const cachedImg = imgCache.get(imgName); if (cachedImg && !cachedImg.deref()) imgCache.delete(imgName); }); return (imgName) => { const cachedImg = imgCache.get(imgName); si (cachedImg?.deref()) { return cachedImg?.deref(); } const newImg = fetchImg(imgName); imgCache.set(imgName, new WeakRef(newImg)); registre.register(newImg, imgName); // (2) retourner newImg ; } ; } const getCachedImg = faibleRefCache(fetchImg);
Pour gérer le nettoyage des entrées de cache « mortes », lorsque les objets WeakRef
associés sont collectés par le garbage collector, nous créons un registre de nettoyage FinalizationRegistry
.
Le point important ici est que lors du rappel de nettoyage, il convient de vérifier si l'entrée a été supprimée par le ramasse-miettes et non rajoutée, afin de ne pas supprimer une entrée « en direct ».
Une fois la nouvelle valeur (image) téléchargée et mise dans le cache, nous l'enregistrons dans le registre du finaliseur pour suivre l'objet WeakRef
.
Cette implémentation ne contient que des paires clé/valeur réelles ou « en direct ». Dans ce cas, chaque objet WeakRef
est enregistré dans FinalizationRegistry
. Et une fois les objets nettoyés par le garbage collector, le rappel de nettoyage supprimera toutes les valeurs undefined
.
Voici une représentation visuelle du code mis à jour :
Un aspect clé de l'implémentation mise à jour est que les finaliseurs permettent de créer des processus parallèles entre le programme « principal » et les rappels de nettoyage. Dans le contexte de JavaScript, le programme « principal » est notre code JavaScript, qui s'exécute dans notre application ou notre page Web.
Par conséquent, à partir du moment où un objet est marqué pour suppression par le garbage collector et jusqu'à l'exécution réelle du rappel de nettoyage, il peut y avoir un certain intervalle de temps. Il est important de comprendre que pendant cet intervalle de temps, le programme principal peut apporter des modifications à l'objet ou même le ramener en mémoire.
C'est pourquoi, lors du rappel de nettoyage, nous devons vérifier si une entrée a été rajoutée au cache par le programme principal pour éviter de supprimer des entrées « live ». De même, lors de la recherche d'une clé dans le cache, il est possible que la valeur ait été supprimée par le ramasse-miettes, mais que le rappel de nettoyage n'ait pas encore été exécuté.
De telles situations nécessitent une attention particulière si vous travaillez avec FinalizationRegistry
.
En passant de la théorie à la pratique, imaginez un scénario réel dans lequel un utilisateur synchronise ses photos sur un appareil mobile avec un service cloud (comme iCloud ou Google Photos) et souhaite les visualiser depuis d'autres appareils. En plus des fonctionnalités de base de visualisation de photos, ces services offrent de nombreuses fonctionnalités supplémentaires, par exemple :
Retouche photo et effets vidéo.
Création de « souvenirs » et d'albums.
Montage vidéo à partir d'une série de photos.
…et bien plus encore.
Ici, à titre d'exemple, nous utiliserons une implémentation assez primitive d'un tel service. Le point principal est de montrer un scénario possible d’utilisation conjointe WeakRef
et FinalizationRegistry
dans la vie réelle.
Voici à quoi cela ressemble :
Sur le côté gauche, il y a une bibliothèque cloud de photos (elles sont affichées sous forme de vignettes). Nous pouvons sélectionner les images dont nous avons besoin et créer un collage en cliquant sur le bouton "Créer un collage" sur le côté droit de la page. Ensuite, le collage obtenu peut être téléchargé sous forme d’image.
Pour augmenter la vitesse de chargement des pages, il serait raisonnable de télécharger et d'afficher les vignettes des photos en qualité compressée . Mais pour créer un collage à partir de photos sélectionnées, téléchargez-les et utilisez-les en taille réelle .
Ci-dessous, on peut voir que la taille intrinsèque des vignettes est de 240x240 pixels. La taille a été choisie exprès pour augmenter la vitesse de chargement. De plus, nous n'avons pas besoin de photos en taille réelle en mode aperçu.
Supposons que nous devions créer un collage de 4 photos : nous les sélectionnons, puis cliquons sur le bouton "Créer un collage". A ce stade, la fonction déjà connue weakRefCache
vérifie si l'image requise est dans le cache. Sinon, il le télécharge depuis le cloud et le met dans le cache pour une utilisation ultérieure. Cela se produit pour chaque image sélectionnée :
En faisant attention à la sortie dans la console, vous pouvez voir quelles photos ont été téléchargées depuis le cloud – ceci est indiqué par FETCHED_IMAGE . Puisqu'il s'agit de la première tentative de création d'un collage, cela signifie qu'à ce stade, le « cache faible » était encore vide et que toutes les photos ont été téléchargées depuis le cloud et placées dedans.
Mais, parallèlement au processus de téléchargement d'images, il existe également un processus de nettoyage de la mémoire par le ramasse-miettes. Cela signifie que l'objet stocké dans le cache, auquel nous faisons référence, en utilisant une référence faible, est supprimé par le garbage collector. Et notre finaliseur s'exécute avec succès, supprimant ainsi la clé par laquelle l'image a été stockée dans le cache. CLEANED_IMAGE nous en informe :
Ensuite, nous réalisons que nous n'aimons pas le collage obtenu et décidons de modifier l'une des images et d'en créer une nouvelle. Pour ce faire, désélectionnez simplement l'image inutile, sélectionnez-en une autre et cliquez à nouveau sur le bouton « Créer un collage » :
Mais cette fois, toutes les images n'ont pas été téléchargées depuis le réseau, et l'une d'entre elles a été extraite du cache faible : le message CACHED_IMAGE nous en parle. Cela signifie qu'au moment de la création du collage, le garbage collector n'avait pas encore supprimé notre image et nous l'avons hardiment retirée du cache, réduisant ainsi le nombre de requêtes réseau et accélérant le temps global du processus de création du collage :
"Jouons" encore un peu, en remplaçant à nouveau l'une des images et en créant un nouveau collage :
Cette fois, le résultat est encore plus impressionnant. Sur les 4 images sélectionnées, 3 d'entre elles ont été extraites du cache faible, et une seule a dû être téléchargée depuis le réseau. La réduction de la charge du réseau était d'environ 75 %. Impressionnant, n'est-ce pas ?
Bien entendu, il est important de se rappeler qu’un tel comportement n’est pas garanti et dépend de la mise en œuvre et du fonctionnement spécifiques du garbage collector.
Partant de là, une question tout à fait logique se pose immédiatement : pourquoi n'utilisons-nous pas un cache ordinaire, où nous pouvons gérer nous-mêmes ses entités, au lieu de nous fier au garbage collector ? C'est vrai, dans la grande majorité des cas, il n'est pas nécessaire d'utiliser WeakRef
et FinalizationRegistry
.
Ici, nous avons simplement démontré une implémentation alternative de fonctionnalités similaires, en utilisant une approche non triviale avec des fonctionnalités de langage intéressantes. Cependant, nous ne pouvons pas nous fier à cet exemple si nous avons besoin d’un résultat constant et prévisible.
Vous pouvez ouvrir cet exemple dans le bac à sable.
WeakRef
– conçu pour créer des références faibles aux objets, leur permettant d'être supprimés de la mémoire par le ramasse-miettes s'il n'y a plus de références fortes à eux. Ceci est bénéfique pour résoudre l’utilisation excessive de la mémoire et optimiser l’utilisation des ressources système dans les applications.
FinalizationRegistry
– est un outil pour enregistrer les rappels, qui sont exécutés lorsque des objets qui ne sont plus fortement référencés sont détruits. Cela permet de libérer les ressources associées à l'objet ou d'effectuer d'autres opérations nécessaires avant de supprimer l'objet de la mémoire.