Comment fonctionne le Garbage Collector en Javascript ?
Qu'est-qu'un garbage collector et comment Javascript gère la libération de sa mémoire ?
Article publié le 15/03/2021, dernière mise à jour le 19/09/2023
Le Garbage Collector, aussi appelé GC, ou parfois "ramasse-miettes" en français désigne un processus automatisé dont le but est de libérer la mémoire dynamique (heap) inutilisée par un programme au fur et à mesure de son exécution.
Si les concepts de primitives, références et de mémoire dynamique (heap) ne vous sont pas familiers, je vous recommande de lire mon article qui leur est dédié !
Dans les langages bas-niveau comme le C par exemple, il est possible de gérer la mémoire dynamique de manière manuelle, en trois phases distinctes :
- Allocation d'un espace mémoire (malloc(...), calloc(...))
- Utilisation (initialisation/écriture puis lecture)
- Libération de la mémoire désormais inutilisée (free(...))
Mais en Javascript, lorsque vous créez une référence vers un nouvel objet, toute l'allocation mémoire est faite de manière automatique, vous n'avez pas à savoir combien d'octets vous avez besoin de réquisitionner.
Et vous n'avez pas non plus besoin de libérer la mémoire dynamique utilisée, car c'est le moteur de Javascript qui s'en charge, grâce à son "Garbage Collector".
À noter que je parle de la mémoire dynamique, car les objets primitifs stockés dans la mémoire statique sont effacés dès lors que vous sortez de la scope dans laquelle ils ont été créés.
Le fonctionnement
La problématique
Avant de se pencher sur la solution, il faut bien comprendre le problème. Prenons une zone mémoire que nous appeleront M1, qui prend 200Ko de place dans la mémoire dynamique et sur laquelle sont pointées 3 références à différents endroits de notre programme.
Pour que notre programme ne soit pas trop gourmand en mémoire, il faut que nous puissions libérer l'énorme place prise dès que cette zone mémoire ne sera plus utilisée.
Le problème étant que le moteur JS seul ne peut pas prévoir en avance à quel moment de l'exécution de notre programme cette zone mémoire ne sera plus pointée par aucune référence.
Voilà pourquoi nous avons besoin d'un Garbage Collector, dont le travail va être de découvrir les cases mémoires non-référencées afin de les libérer.
Une approche simple, le reference-counting
Il existe des garbages collectors dans beaucoup de langages, et chacun implémente un algorithme de détection différent, parfois plus ou moins efficace_._ L'une des premières approche qui a été utilisée, est le "reference-counting" (le comptage de références).
Cet algorithme consiste tout simplement à stocker, pour chaque zone de la mémoire dynamique utilisée, le nombre de références qui pointent vers cette zone, puis d'incrémenter ce chiffre à chaque nouvelle référence ou et de le décrémenter à chaque suppression d'une référence.
Lorsque le nombre de références tombe à 0, alors c'est qu'il est temps de libérer cette zone mémoire.
Cette approche est fonctionnelle mais elle pose quelques problèmes, dont deux principaux :
- Le garbage collector est en activité quasi-constante, parfois pour libérer une seule petite zone mémoire, perdant donc de l'efficacité
- L'algorithme est incapable de libérer les zones mémoires pointées par des références circulaires
Une référence circulaire peut-être créée lorsqu'un objet se référence lui-même, ou lorsque deux objets contiennent chacun une référence vers l'un vers l'autre.
Dans le cas d'une référence circulaire, le nombre de références ne peut jamais tomber à zéro et la zone mémoire ne sera jamais libérée, c'est ce que l'on appelle une fuite de mémoire (il en existe de beaucoup d'autres types).
Pour palier à celà, Javascript implémente à la place un autre algorithme appelé "Mark and Sweep".
L'approche Mark and Sweep
Javascript, peu importe son environnement d'exécution (Navigateur web ou NodeJS), met à disposition un objet racine auquel toutes les variables et les fonctions de premier niveau sont rattachées.
Ces objets sont "window" dans le navigateur et "process" en NodeJS
Ce qui signifie que n'importe quel objet d'une application Javascript doit rester accessible en remontant depuis l'objet racine, jusqu'à l'objet en traversant toutes les références nécessaires.
Si on le prend à l'envers, celà signifie que toute zone mémoire allouée par Javascript mais non-atteignable depuis l'objet racine est en fait une zone qui peut être libérée !
L'algorithme de Mark and Sweep du garbage collector consiste donc à "flaguer" tous les objets inatteignables depuis la racine pour qu'ils soit tous collectés en même temps dès que Javascript aura besoin de libérer de la mémoire.
Cette approche résout donc les deux problèmes présentés dans l'approche de "reference-counting", car une référence circulaire est coupé de l'objet racine dès lors qu'aucune autre référence pointe vers cette dernière.
À noter que le passage du garbage collector est donc imprévisible, et que son exécution peut introduire d'autres effets de bord comme le ralentissement de l'application à ce moment-là.
Aucun commentaire pour l'instant