Maison  >  Article  >  interface Web  >  Parlons du mécanisme GC (garbage collection) dans Node.js

Parlons du mécanisme GC (garbage collection) dans Node.js

青灯夜游
青灯夜游avant
2022-11-29 20:44:082108parcourir

Comment

Node fait-il le GC (garbage collection) ? L’article suivant vous guidera à travers cela.

Parlons du mécanisme GC (garbage collection) dans Node.js

GC, Garbage Collection, collecte des déchets. En programmation, il fait généralement référence au mécanisme de recyclage automatique de la mémoire, qui efface régulièrement les données inutiles.

Node.js utilise le moteur V8 en dessous. V8 est un moteur JavaScript hautes performances open source par Google et écrit en C++. [Recommandations du didacticiel associé : Tutoriel vidéo Nodejs]

La mémoire Node.js est principalement divisée en trois parties :

  • espace de code : où sont stockés les segments de code

  • pile : variables temporaires générées par l'appel de fonction ; stack, Pour certains types de base, tels que les nombres, les chaînes, les valeurs booléennes et les références d'objet (l'adresse est enregistrée, pas l'objet lui-même).

  • Heap : stocke des données telles que des objets ;

Heap memory

Node.js utilise V8 en bas. Expliquons le mécanisme de recyclage de la mémoire de V8.

Tout d'abord, tous les objets en JS seront stockés dans la mémoire tas. Lorsque le processus est créé, une taille initiale de mémoire tas sera allouée, puis nos objets y seront placés.

Lorsqu'il y a de plus en plus d'objets, la mémoire du tas ne suffira pas et la mémoire du tas s'étendra dynamiquement. Si une limite maximale est atteinte (généralement 4 Go de nos jours), une erreur de débordement de tas se produit et le processus Node.js est terminé.

Nouvelle génération et ancienne génération

V8 divise d'abord la mémoire en deux parties, ou deux générations :

  • Jeune génération : sauvegarde certains objets avec un temps de survie court

  • Ancienne génération : sauvegarde les objets qui ont un temps de survie court ; une longue durée de survie ou sont permanents.

La nouvelle génération est très petite et certains objets avec des temps de survie courts sont stockés ici. Ils sont généralement recyclés fréquemment (comme certains objets temporaires dans la pile d'appels de fonctions).

La taille de la nouvelle génération peut être modifiée via node --max-semi-space-size=SIZE index.js, en Mo. node --max-semi-space-size=SIZE index.js 修改新生代的大小,单位为 MB。

另外,老生代则通过 --max-old-space-size=SIZE

De plus, l'ancienne génération utilise --max-old-space-size=SIZE pour définir l'algorithme Scavenge de la nouvelle génération

La nouvelle génération utilise l'algorithme Scavenge, qui est un algorithme basé sur la copie.

La nouvelle génération sera divisée en deux espaces. Cet espace est appelé semi-espace. Ils sont :

    Depuis l'espace : les objets nouvellement déclarés seront placés ici
  • Vers l'espace : espace utilisé pour la relocalisation
  • L'objet nouvellement déclaré sera placé dans l'espace From. Les objets dans l'espace From sont étroitement disposés grâce au pointeur, l'objet précédent est proche de l'objet suivant. La mémoire est continue, il n'y a donc pas lieu de s'inquiéter de la mémoire. fragmentation.

La soi-disant fragmentation de la mémoire fait référence à une allocation inégale de l'espace, entraînant un grand nombre de petits espaces continus qui ne peuvent pas tenir dans un grand objet.

Lorsque l'espace From est presque plein, nous allons parcourir pour trouver les objets actifs et les copier dans l'espace To. À ce stade, l’espace From est en fait vide, puis nous échangeons les identités From et To.

Si certains objets sont copiés plusieurs fois, ils seront considérés comme ayant une durée de survie plus longue et seront déplacés vers l'ancienne génération.

L'avantage de cet algorithme basé sur la copie est qu'il peut très bien gérer le problème de la fragmentation de la mémoire. L'inconvénient est qu'il gaspillera de l'espace en tant qu'espace de déplacement. De plus, comme la copie prend du temps, ce n'est pas le cas. adapté pour allouer un espace mémoire trop grand. Il s'agit plutôt d'un GC auxiliaire.

Mark-Sweep et Mark-Compact

L'espace de l'ancienne génération est beaucoup plus grand que celui de la nouvelle génération. Il contient des objets à longue durée de vie et utilise l'algorithme Mark-Sweep (effacement des marques).

La première étape est l’étape de marquage. Recherchez tous les objets accessibles à partir de l'ensemble racine (pile d'exécution et objets globaux) et marquez-les comme objets actifs.

Après le marquage, c'est la phase d'effacement. Effacer les objets non marqués marque en fait l'adresse mémoire comme libre.

Cette approche conduira à une

fragmentation de l'espace mémoire libre

Lorsque nous créons un grand objet continu, nous ne pourrons pas trouver d'endroit où le poser. À ce stade, Mark-Compact (compactage des marques) doit être utilisé pour intégrer les objets actifs fragmentés. Mark-Compact déplacera toutes les copies d'objets actives vers une extrémité, puis l'autre côté de la limite sera un bloc entier de mémoire disponible contiguë.

Étant donné que Mark-Sweep et Mark-Compact prennent beaucoup de temps et bloquent le fil JavaScript, nous ne le faisons généralement pas en même temps, mais utilisons un marquage incrémentiel (Incremental Marking). Cela signifie marquer par intermittence, procéder par petites étapes et alterner le garbage collection et la logique d'application.

De plus, V8 effectue également un marquage parallèle et un nettoyage parallèle pour améliorer l'efficacité d'exécution.

Parlons du mécanisme GC (garbage collection) dans Node.js

Afficher les informations relatives à la mémoire

Nous pouvons obtenir des informations relatives à la mémoire grâce à la méthode process.memoryUsage.

process.memoryUsage();

Le contenu de sortie est :

{
  rss: 35454976,
  heapTotal: 7127040,
  heapUsed: 5287088,
  external: 958852,
  arrayBuffers: 11314
}

Instructions

  • rss : taille de l'ensemble résident (taille de l'ensemble résident), y compris les extraits de code, la mémoire du tas, la pile, etc.

  • heapTotal : la taille totale de la mémoire du tas de V8 ;

  • heapUsed : la mémoire du tas occupée

  • external : la taille de la mémoire autre que V8, qui fait référence à la mémoire occupée par les objets C++ ; Données tampon .

  • arrayBuffers : La taille de la mémoire liée à ArrayBuffer et SharedArrayBuffer, qui fait partie de l'externe. ArrayBufferSharedArrayBuffer 相关的内存大小,属于 external 的一部分。

以上数字的单位都是字节。

测试最大内存限制

写一个脚本,用一个定时器,让一个数组不停地变大,并打印堆内存使用情况,直到内存溢出。

const format = function (bytes) {
  return (bytes / 1024 / 1024).toFixed(2) + " MB";
};

const printMemoryUsage = function () {
  const memoryUsage = process.memoryUsage();
  console.log(
    `heapTotal: ${format(memoryUsage.heapTotal)}, heapUsed: ${format(
      memoryUsage.heapUsed
    )}`
  );
};

const bigArray = [];
setInterval(function () {
  bigArray.push(new Array(20 * 1024 * 1024));
  printMemoryUsage();
}, 500);

需要特别注意的是,不要用 Buffer 做测试。

因为 Buffer 是 Node.js 特有的处理二进制的对象,它不是在 V8 中的实现的,是 Node.js 用 C++ 另外实现的,不通过 V8 分配内存,属于堆外内存。

我使用电脑是 macbook pro M1 Pro,Node.js 版本为 v16.17.0

Les unités des nombres ci-dessus sont des octets.

Testez la limite maximale de mémoire

Écrivez un script, utilisez une minuterie, faites croître un tableau en continu et imprimez l'utilisation de la mémoire tas jusqu'à ce que la mémoire déborde.

heapTotal: 164.81 MB, heapUsed: 163.93 MB
heapTotal: 325.83 MB, heapUsed: 323.79 MB
heapTotal: 488.59 MB, heapUsed: 483.84 MB
...
heapTotal: 4036.44 MB, heapUsed: 4003.37 MB
heapTotal: 4196.45 MB, heapUsed: 4163.29 MB

<--- Last few GCs --->

[28033:0x140008000]    17968 ms: Mark-sweep 4003.2 (4036.4) -> 4003.1 (4036.4) MB, 2233.8 / 0.0 ms  (average mu = 0.565, current mu = 0.310) allocation failure scavenge might not succeed
[28033:0x140008000]    19815 ms: Mark-sweep 4163.3 (4196.5) -> 4163.1 (4196.5) MB, 1780.3 / 0.0 ms  (average mu = 0.413, current mu = 0.036) allocation failure scavenge might not succeed


<--- JS stacktrace --->

FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
...

Une attention particulière doit être portée à ne pas utiliser Buffer pour les tests. Parce que Buffer est un objet binaire unique à Node.js. Il n'est pas implémenté dans la V8. Il est implémenté séparément par Node.js en utilisant C++ et n'alloue pas de mémoire via la V8 et appartient à la mémoire hors tas. 🎜
🎜L'ordinateur que j'utilise est le macbook pro M1 Pro, la version Node.js est v16.17.0 et la version V8 utilisée est 9.4.146.26-node.22 (via process. versions.v8 obtenir). 🎜🎜Le résultat de sortie est (certaines informations redondantes sont omises) : 🎜rrreee🎜Comme vous pouvez le voir, la limite de mémoire est dépassée après 4 000 Mo, un débordement de tas se produit et le processus se termine. Notez que sur ma machine, la mémoire maximale par défaut est de 4G. 🎜🎜La mémoire maximale réelle est liée à la machine sur laquelle elle fonctionne. Si la taille de la mémoire de votre machine est de 2 Go, la mémoire maximale sera définie sur 1,5 Go. 🎜🎜Pour plus de connaissances sur les nœuds, veuillez visiter : 🎜tutoriel Nodejs🎜 ! 🎜

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer