La mémoire en NodeJS

Fonctionnement de NodeJS

NodeJS est un runtime qui permet d'exécuter du code JavaScript en dehors d’un navigateur à l’aide du moteur V8 de Google. Il est divisé en plusieurs parties dont les principales sont :

- Une API qui correspond aux fonctions NodeJS auxquelles les programmes peuvent accéder
- Un binding de l’API NodeJS avec les API Natives (généralement écrites en C / C++) qui permet de lier NodeJS à la couche native
- Une couche native permettant de mettre des librairies à disposition telles que libuv, crypto, ou encore c-area.

Visuel1.png

Le code NodeJS est interprété par V8 qui est un moteur JavaScript libre et open source développé en c++ pour les architectures x86 et ARM par Google au Danemark. Ce moteur est également utilisé par les navigateurs Chrome et Chromium. Ce n’est cependant pas le seul moteur disponible sur le marché. En effet, d’autres moteurs existent tels que :

- SpiderMonkey qui est le 1er moteur JavaScript écrit en langage C pour Netscape
- Chakra de Microsoft qui peut également interpréter du code NodeJS https://github.com/nodejs/node-chakracore
- JavaScriptCore

Les bases sur la mémoire

La mémoire est divisée en deux zones principales nommées la stack (pile) et la heap (tas).

Visuel2.png

La stack est une zone de la mémoire qui contient les variables locales des fonctions. A chaque fois qu’une fonction est appelée, une nouvelle frame contenant les paramètres et variables locales à cette fonction est poussée dans la stack. Lorsque l’exécution de la fonction est terminée, la frame est supprimée de la stack. La stack fonctionne donc en LIFO (Last In First Out) et l’accès à ses ressources est très rapide.

La heap contient tous les objets globaux ainsi que leurs enfants. Les objets alloués dans la heap sont donc accessibles depuis n’importe ou dans le code à partir du moment ou l’on possède la référence pour y accéder. L’accès aux éléments de la heap est plus lent que celui aux éléments de la stack.

Création de deux instances de Voiture dans la heap:
Visuel3.png
Visuel4.png

Si on prend l’exemple ci-dessus, les deux objets “voit1” et “voit2” seront alors reliés aux objets globaux nommés “Roots” sur le schéma. Il est donc désormais possible d’accéder aux objets “voit1” et “voit2” depuis n’importe ou dans notre code à partir du moment ou l’on possède la bonne référence. Si nous décidons d’assigner la valeur “undefined” à la voiture 1, la référence entre cette voiture et “Roots” sera alors supprimée.

Visuel5.png
Visuel6.png

Si l’on simplifie le découpage de la heap, on peut y trouver deux zones mémoires qui sont nommées “New space” et “Old space”.
Visuel7.png

New space

La new space est la zone qui va contenir la plupart des nouveaux objets alloués. Après un certain temps, si les objets de cette zone possèdent toujours une référence vers les objets “Roots”, ils seront alors placés dans la old space qui les conservera sur le long terme. Les nouveaux objets sont placés dans la new space en partant du principe que la nouvelle génération a plus de chance de mourir que l’ancienne génération, c’est ce que l’on appelle le principe de mortalité infantile.

Old space

La old space est donc la zone qui va contenir les objets qui ont survécus un certain temps dans la new space, ils seront alors conservés jusqu’à ce que le programme n’en ai plus besoin.

Garbage Collector

Le Garbage Collector est un programme qui permet une gestion automatique de la mémoire. Son rôle est de libérer la mémoire occupée et non utilisée dans un programme, ce qui évite au développeur de le faire explicitement. Les régions de la mémoire collectées par le Garbage Collector sont soit ré-utilisées pour d’autres objets soit rendues au système d’exploitation. Pour gérer la mémoire le Garbage Collector utilise l’algorithme de mark and sweep qui fonctionne de la façon suivante:

- Construction d’une liste d’objets globaux nommés “roots” auxquels une référence est conservée
- Tous les objets de cette liste ainsi que leurs enfants sont marqué récursivement comme actifs

Visuel8.png
- La mémoire des objets qui n’ont pas été marqués comme actif est donc libérée

Visuel9.png

La collection de la old space est une action gourmande en ressources, elle est donc lancée uniquement lorsque la mémoire maximale de la old space est atteinte. Par défaut la mémoire maximale est de 700 Mo sur les systèmes 32 bits et de 1,4 Go sur les systèmes 64 bits. Il est possible d’augmenter sa taille au lancement du programme à l’aide de la commande suivante :

Visuel10.png

Conclusion avec deux outils pour analyser la mémoire de son application

Heapdump (https://www.npmjs.com/package/heapdump)

Heapdump est un module NPM qui permet de créer un snapshot de la heap. Ce Snapshot pourra ensuite être importé dans l’onglet “Profile” des Chrome Dev Tools pour être analysé plus minutieusement.

Visuel11.png

PM2

(https://www.npmjs.com/package/pm2)

PM2 est un process manager NodeJS proposant de nombreuses fonctionnalités telles que du Monitoring, une gestion des logs, du clustering d’applications et bien plus encore.

Visuel12.png

Marc Hanin
DevOps Developer - Claranet