La gestion de la mémoire en JavaScript est effectuée automatiquement et de manière invisible pour nous. Nous créons des primitives, des objets, des fonctions⦠Tout cela prend de la mémoire.
Que se passe-t-il quand quelque chose nâest plus nécessaire ? Comment le moteur JavaScript le découvre et le nettoie ?
Accessibilité
Le concept principal de la gestion de la mémoire en JavaScript est lâaccessibilité.
En termes simples, les valeurs âaccessiblesâ sont celles qui sont accessibles ou utilisables dâune manière ou dâune autre. Elles sont garanties dâêtre stockés en mémoire.
-
Il existe un ensemble de base de valeurs intrinsèquement accessibles, qui ne peuvent pas être supprimées pour des raisons évidentes.
Par exemple :
- Variables locales et paramètres de la fonction en cours dâexécution.
- Variables et paramètres pour dâautres fonctions sur la chaîne dâappels imbriqués en cours.
- Variables globales.
- (il y en a dâautres, internes aussi)
Ces valeurs sâappellent des racines (roots).
-
Toute autre valeur est considérée comme accessible si elle est accessible depuis une racine par une référence ou par une chaîne de références.
Par exemple, sâil existe un objet dans une variable globale et que cet objet a une propriété référençant un autre objet, cet objet est considéré comme accessible. Et ceux auxquels il fait référence sont également accessibles. Des exemples détaillés à suivre.
Il existe un processus dâarrière-plan dans le moteur JavaScript appelé Ramasse-miettes (Garbage Collector). Il surveille tous les objets et supprime ceux qui sont devenus inaccessibles.
Un exemple simple
Voici lâexemple le plus simple :
// user a une référence à l'objet
let user = {
name: "John"
};
Ici, la flèche représente une référence dâobjet. La variable globale "user" fait référence à lâobjet {name: "John"} (nous lâappellerons John par souci de brièveté). La propriété "name" de John stocke une primitive, elle est donc stockée à lâintérieur de lâobjet.
Si la valeur de user est écrasée, la référence est perdue :
user = null;
Maintenant, John devient inaccessible. Il nây a aucun moyen dây accéder, pas de référence. Le ramasse-miettes (garbage collector) détruit les données et libère la mémoire.
Deux références
Imaginons maintenant que nous ayons copié la référence de user à admin :
// user a une référence à l'objet
let user = {
name: "John"
};
let admin = user;
Maintenant si nous faisons la même chose :
user = null;
⦠Ensuite, lâobjet est toujours accessible via la variable globale admin, il est donc encore en mémoire. Si nous écrasons également admin, alors il sera supprimé.
Objets liés
Maintenant, un exemple plus complexe. La famille :
function marry(man, woman) {
woman.husband = man;
man.wife = woman;
return {
father: man,
mother: woman
}
}
let family = marry({
name: "John"
}, {
name: "Ann"
});
La fonction marry âmarieâ deux objets en leur donnant des références et renvoie un nouvel objet les contenant tous les deux.
Le résultat de la structure de mémoire :
à partir de maintenant, tous les objets sont accessibles.
Supprimons maintenant deux références :
delete family.father;
delete family.mother.husband;
Il ne suffit pas de supprimer une seule de ces deux références, car tous les objets seraient toujours accessibles.
Mais si nous supprimons les deux, alors nous pouvons voir que John nâa plus de référence entrante :
Les références sortantes importent peu. Seuls les objets entrants peuvent rendre un objet accessible. Ainsi, John est maintenant inaccessible et sera supprimé de la mémoire avec toutes ses données qui sont également devenues inaccessibles.
Après le passage du ramasse-miettes (garbage collector) :
Ãle inaccessible
Il est possible que toute lâîle dâobjets liés entre eux devienne inaccessible et soit supprimée de la mémoire.
Lâobjet source est le même que ci-dessus. Ensuite :
family = null;
Lâimage en mémoire devient :
Cet exemple montre à quel point le concept dâaccessibilité est important.
Il est évident que John et Ann sont toujours liés, les deux ont des références entrantes. Mais cela ne suffit pas.
Lâancien objet "family" a été dissocié de la racine, elle nây fait plus référence, toute lâîle devient inaccessible et sera donc supprimée.
Algorithmes internes
Lâalgorithme de base de la récupération de place (garbage collection) sâappelle âmark-and-sweepâ.
Les étapes suivantes du âramasse-miettesâ (garbage collection) sont régulièrement effectuées :
- Le ramasse-miettes prend les racines et les âmarqueâ (se souvient).
- Ensuite, il visite et âmarqueâ toutes les références.
- Ensuite, il visite les objets marqués et marque leurs références. Tous les objets visités sont mémorisés afin de ne pas visiter le même objet deux fois dans le futur.
- ⦠Et ainsi de suite tant quâil y a des références non consultées (accessibles depuis les racines).
- Tous les objets sont supprimés sauf ceux qui sont marqués.
Par exemple, imaginons notre structure dâobjet ressembler à ceci :
Nous pouvons clairement voir une âîle inaccessibleâ sur le côté droit. Voyons maintenant comment le garbage collector âmark-and-sweepâ le gère.
La première étape marque les racines :
Ensuite, nous suivons leurs références et marquons les objets référencés :
â¦Et continuons à suivre dâautres références, dans la mesure du possible :
Désormais, les objets qui nâont pas pu être visités sont considérés comme inaccessibles et seront supprimés :
Nous pouvons également imaginer que le processus consiste à renverser un énorme seau de peinture à la racine, qui traverse toutes les références et marque tous les objets accessibles. Les non marqués sont ensuite supprimés.
Câest le concept de la façon dont la garbage collection fonctionne. Les moteurs JavaScript appliquent de nombreuses optimisations pour accélérer lâexécution et ne pas affecter lâexécution.
Certaines des optimisations :
- Collecte générationnelle â les objets sont divisés en deux ensembles : ânouveauxâ et âanciensâ. Dans un code typique, de nombreux objets ont une courte durée de vie : ils apparaissent, font leur travail et meurent rapidement, il est donc logique de suivre les nouveaux objets et dâen effacer la mémoire si câest le cas. Ceux qui survivent assez longtemps deviennent âvieuxâ et sont examinés moins souvent.
- Collecte incrémentielle â sâil y a beaucoup dâobjets et que nous essayons de parcourir et de marquer lâensemble dâobjets en une seule fois, cela peut prendre un certain temps et introduire des retards visibles dans lâexécution. Ainsi, le moteur divise lâensemble des objets existants en plusieurs parties. Et puis nettoie ces parties les unes après les autres. Il existe de nombreux petits garbage collections au lieu dâun total. Cela nécessite une comptabilité supplémentaire entre eux pour suivre les changements, mais nous obtenons de nombreux petits retards au lieu dâun gros.
- Collecte en cas dâinactivité â le garbage collector essaie de sâexécuter uniquement lorsque le processeur est inactif, afin de réduire lâeffet possible sur lâexécution.
Il existe dâautres optimisations et variantes dâalgorithmes de récupération de place. Même si je souhaite les décrire ici, je dois mâabstenir, car différents moteurs implémentent différentes techniques et ajustements. Et, ce qui est encore plus important, les choses changent à mesure que les moteurs se développent. Donc aller plus loin de manière plus poussée, sans réel besoin, nâen vaut probablement pas la peine. à moins, bien sûr, que ce soit une question qui vous intéresse vraiment, vous trouverez quelques liens pour vous ci-dessous.
Résumé
Les principales choses à savoir :
- La garbage collection est effectuée automatiquement. Nous ne pouvons ni forcer ni empêcher cela.
- Les objets sont conservés en mémoire tant quâils sont accessibles.
- Ãtre référencé nâest pas la même chose quâêtre accessible (depuis une racine) : un groupe dâobjets liés entre eux peut devenir inaccessible dans son ensemble.
Les moteurs modernes implémentent des algorithmes avancés de récupération de place.
Un livre général intitulé âThe Garbage Collection Handbook: The Art of Automatic Memory Managementâ (R. Jones et al.) En parle.
Si vous êtes familiarisé avec la programmation de bas niveau, les informations plus détaillées sur le garbage collector V8 se trouvent dans lâarticle A tour of V8: Garbage Collection.
Le blog V8 publie également des articles sur les modifications de la gestion de la mémoire de temps à autre. Naturellement, pour apprendre la récupération de place, vous feriez mieux de vous préparer en vous renseignant sur les éléments internes de V8 en général et en lisant le blog de Vyacheslav Egorov qui a travaillé comme lâun des ingénieurs V8. Je dis: «V8», car câest le plus couvert dâarticles sur Internet. Pour dâautres moteurs, de nombreuses approches sont similaires, mais la récupération de place diffère à de nombreux égards.
Une connaissance approfondie des moteurs est utile lorsque vous avez besoin dâoptimisations de bas niveau. Il serait sage de planifier cela comme prochaine étape après la connaissance du langage.
Commentaires
<code>, pour plusieurs lignes â enveloppez-les avec la balise<pre>, pour plus de 10 lignes - utilisez une sandbox (plnkr, jsbin, codepenâ¦)