Maison > interface Web > js tutoriel > le corps du texte

Explorez l'allocation de mémoire tas dans Node et parlez des limites de mémoire !

青灯夜游
Libérer: 2022-01-11 19:21:11
avant
4233 Les gens l'ont consulté

Cet article vous amènera à explorer l'allocation de mémoire du tas dans Node et à acquérir une compréhension approfondie des limitations de mémoire dans Node.js. J'espère qu'il vous sera utile !

Explorez l'allocation de mémoire tas dans Node et parlez des limites de mémoire !

Dans cet article, j'explorerai l'allocation de mémoire du tas dans Node, puis j'essaierai d'augmenter la mémoire jusqu'à la limite que le matériel peut supporter. Nous trouverons ensuite quelques moyens pratiques de surveiller les processus de Node afin de déboguer les problèmes liés à la mémoire.

OK, préparez-vous et partez !

Vous pouvez extraire le code correspondant dans l'entrepôt et cloner le code depuis mon GitHub :

https://github.com/beautifulcoder/node-memory-limitations

Introduction au garbage collection V8

Premier , une brève introduction Découvrez le garbage collector V8. La méthode d'allocation de stockage de la mémoire est le tas, qui est divisé en plusieurs zones générationnelles. Un objet change d’âge au cours de son cycle de vie, et la génération à laquelle il appartient change également.

Les générations sont divisées en la jeune génération et l'ancienne génération, et la jeune génération est également divisée en la nouvelle génération et la génération intermédiaire. À mesure que les objets survivent au ramassage des déchets, ils rejoignent également l’ancienne génération.

Explorez lallocation de mémoire tas dans Node et parlez des limites de mémoire !

Le principe de base de l'hypothèse générationnelle est que la plupart des sujets sont jeunes. Le garbage collector V8 s'appuie sur cela en promouvant uniquement les objets qui survivent à un garbage collection. À mesure que les objets sont copiés dans des régions adjacentes, ils finissent par se retrouver dans l'ancienne génération.

La consommation de mémoire dans Nodejs est principalement divisée en trois aspects :

  • Code - où le code est exécuté
  • Pile d'appels - utilisée pour stocker des fonctions avec des types primitifs (nombres, chaînes ou valeurs booléennes) et des variables locales
  • Mémoire tas

La mémoire tas est notre objectif principal aujourd'hui. Maintenant que vous en savez un peu plus sur le garbage collector, il est temps d'allouer de la mémoire sur le tas !

function allocateMemory(size) {
  // Simulate allocation of bytes
  const numbers = size / 8;
  const arr = [];
  arr.length = numbers;
  for (let i = 0; i < numbers; i++) {
    arr[i] = i;
  }
  return arr;
}
Copier après la connexion

Dans la pile d'appels, les variables locales sont détruites à la fin de l'appel de fonction. Le type sous-jacent number ne va jamais dans la mémoire tas, mais est alloué dans la pile d'appels. Mais l'objet arr ira dans le tas et pourra survivre au garbage collection. number永远不会进入堆内存,而是在调用栈中分配。但是对象arr将进入堆中并且可能在垃圾回收中幸存下来。

堆内存有限制吗?

现在进行勇敢测试——将 Node 进程推到极限看看在哪个地方会耗尽堆内存:

const memoryLeakAllocations = [];

const field = "heapUsed";
const allocationStep = 10000 * 1024; // 10MB

const TIME_INTERVAL_IN_MSEC = 40;

setInterval(() => {
  const allocation = allocateMemory(allocationStep);

  memoryLeakAllocations.push(allocation);

  const mu = process.memoryUsage();
  // # bytes / KB / MB / GB
  const gbNow = mu[field] / 1024 / 1024 / 1024;
  const gbRounded = Math.round(gbNow * 100) / 100;

  console.log(`Heap allocated ${gbRounded} GB`);
}, TIME_INTERVAL_IN_MSEC);
Copier après la connexion

在上面的代码中,我们以 40 毫秒的间隔分配了大约 10 mb,为垃圾回收提供了足够的时间来将幸存的对象提升到老年代。process.memoryUsage

Y a-t-il une limite sur la mémoire tas ?

Maintenant, pour un test courageux : poussez le processus Node jusqu'à la limite et voyez où il manque de mémoire tas :

Heap allocated 4 GB
Heap allocated 4.01 GB

<--- Last few GCs --->

[18820:000001A45B4680A0] 26146 ms: Mark-sweep (reduce) 4103.7 (4107.3) -> 4103.7 (4108.3) MB, 1196.5 / 0.0 ms (average mu = 0.112, current mu = 0.000) last resort GC in old space requested

<--- JS stacktrace --->

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory
Copier après la connexion

Dans le code ci-dessus, nous avons alloué environ 10 Mo à des intervalles de 40 ms pour le garbage collection. Un temps suffisant est fourni promouvoir les objets survivants auprès de l’ancienne génération. process.memoryUsage est un outil permettant de recycler un indicateur approximatif de l'utilisation du tas. À mesure que l'allocation du tas augmente, le champ heapUsed enregistre la taille du tas. Ce champ enregistre le nombre d'octets dans la RAM et peut être converti en Mo.

Vos résultats peuvent varier. Sur un ordinateur portable Windows 10 avec 32 Go de RAM, vous obtiendrez le résultat suivant :

node index.js --max-old-space-size=8000
Copier après la connexion

Ici, le ramasse-miettes tentera de compacter la mémoire en dernier recours avant de finalement abandonner et de lancer une exception "Out of Heap". Ce processus a atteint la limite de 4,1 Go et il a fallu 26,6 secondes pour se rendre compte que le service était sur le point d'être raccroché. Certaines des raisons conduisant aux résultats ci-dessus sont encore inconnues. Le garbage collector V8 fonctionnait à l'origine dans un processus de navigateur 32 bits avec des contraintes de mémoire strictes. Ces résultats suggèrent que les limitations de mémoire peuvent avoir été héritées du code existant.

Au moment de la rédaction, le code ci-dessus s'exécute sous la dernière version de LTS Node et utilise un exécutable 64 bits. En théorie, un processus 64 bits devrait pouvoir allouer plus de 4 Go d'espace et atteindre facilement 16 To d'espace d'adressage.

Agrandir la limite d'allocation de mémoire

Heap allocated 7.8 GB
Heap allocated 7.81 GB

<--- Last few GCs --->

[16976:000001ACB8FEB330] 45701 ms: Mark-sweep (reduce) 8000.2 (8005.3) -> 8000.2 (8006.3) MB, 1468.4 / 0.0 ms (average mu = 0.211, current mu = 0.000) last resort GC in old space requested

<--- JS stacktrace --->

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory
Copier après la connexion

Cela définit la limite maximale à 8 Go. Soyez prudent lorsque vous faites cela. Mon ordinateur portable dispose de 32 Go d'espace. Je recommande de le régler sur la quantité d'espace réellement disponible dans la RAM. Une fois la mémoire physique épuisée, les processus commencent à occuper de l'espace disque via la mémoire virtuelle. Si vous définissez la limite trop élevée, vous obtiendrez une nouvelle raison de changer d'ordinateur. Ici, nous essayons d'éviter que l'ordinateur ne fume ~

Exécutons à nouveau le code avec la limite de 8 Go :

const path = require("path");
const fs = require("fs");
const os = require("os");

const start = Date.now();
const LOG_FILE = path.join(__dirname, "memory-usage.csv");

fs.writeFile(LOG_FILE, "Time Alive (secs),Memory GB" + os.EOL, () => {}); // 请求-确认
Copier après la connexion
Copier après la connexion
🎜Cette fois, la taille du tas est presque proche. à 8 Go, mais pas tout à fait. Je soupçonne qu'il y a une certaine surcharge dans le processus Node pour allouer autant de mémoire. Cette fois, le processus a duré 45,7 secondes. 🎜🎜Dans un environnement de production, cela peut prendre au moins une minute pour manquer de mémoire. C'est l'une des raisons pour lesquelles il est utile de surveiller et d'obtenir un aperçu de la consommation de mémoire. La consommation de mémoire augmentera lentement au fil du temps et plusieurs jours peuvent s'écouler avant que vous vous rendiez compte de l'existence d'un problème. Si votre processus continue de planter et que des exceptions « hors tas » apparaissent dans les journaux, il peut y avoir une fuite de mémoire dans votre code. 🎜🎜Le processus peut également utiliser plus de mémoire car il traite plus de données. Si la consommation de ressources continue de croître, il est peut-être temps de diviser ce monolithe en microservices. Cela réduira la pression de la mémoire sur les processus individuels et permettra aux nœuds d'évoluer horizontalement. 🎜

如何跟踪 Node.js 内存泄漏

process.memoryUsage 的 heapUsed 字段还是有点用的,调试内存泄漏的一个方法是将内存指标放在另一个工具中以进行进一步处理。由于此实现并不复杂,因此主要解析下如何亲自实现。

const path = require("path");
const fs = require("fs");
const os = require("os");

const start = Date.now();
const LOG_FILE = path.join(__dirname, "memory-usage.csv");

fs.writeFile(LOG_FILE, "Time Alive (secs),Memory GB" + os.EOL, () => {}); // 请求-确认
Copier après la connexion
Copier après la connexion

为了避免将堆分配指标放在内存中,我们选择将结果写入 CSV 文件以方便数据消耗。这里使用了 writeFile 带有回调的异步函数。回调为空以写入文件并继续,无需任何进一步处理。 要获取渐进式内存指标,请将其添加到 console.log:

const elapsedTimeInSecs = (Date.now() - start) / 1000;
const timeRounded = Math.round(elapsedTimeInSecs * 100) / 100;

s.appendFile(LOG_FILE, timeRounded + "," + gbRounded + os.EOL, () => {}); // 请求-确认
Copier après la connexion

上面这段代码可以用来调试内存泄漏的情况下,堆内存随着时间变化而增长。你可以使用一些分析工具来解析原生csv数据以实现一个比较漂亮的可视化。

如果你只是赶着看看数据的情况,直接用excel也可以,如下图:

Explorez lallocation de mémoire tas dans Node et parlez des limites de mémoire !

在限制为4.1GB的情况下,你可以看到内存的使用率在短时间内呈线性增长。内存的消耗在持续的增长并没有变得平缓,这个说明了某个地方存在内存泄漏。在我们调试这类问题的时候,我们要寻找在分配在老世代结束时的那部分代码。

对象如果再在垃圾回收时幸存下来,就可能会一直存在,直到进程终止。

使用这段内存泄漏检测代码更具复用性的一种方法是将其包装在自己的时间间隔内(因为它不必存在于主循环中)。

setInterval(() => {
  const mu = process.memoryUsage();
  // # bytes / KB / MB / GB
  const gbNow = mu[field] / 1024 / 1024 / 1024;
  const gbRounded = Math.round(gbNow * 100) / 100;

  const elapsedTimeInSecs = (Date.now() - start) / 1000;
  const timeRounded = Math.round(elapsedTimeInSecs * 100) / 100;

  fs.appendFile(LOG_FILE, timeRounded + "," + gbRounded + os.EOL, () => {}); // fire-and-forget
}, TIME_INTERVAL_IN_MSEC);
Copier après la connexion

要注意上面这些方法并不能直接在生产环境中使用,仅仅只是告诉你如何在本地环境调试内存泄漏。在实际实现时还包括了自动显示、警报和轮换日志,这样服务器才不会耗尽磁盘空间。

跟踪生产环境中的 Node.js 内存泄漏

尽管上面的代码在生产环境中不可行,但我们已经看到了如何去调试内存泄漏。因此,作为替代方案,可以将 Node 进程包裹在 PM2 之类守护进程 中。

当内存消耗达到限制时设置重启策略:

pm2 start index.js --max-memory-restart 8G
Copier après la connexion

单位可以是 K(千字节)、M(兆字节)和 G(千兆字节)。进程重启大约需要 30 秒,因此通过负载均衡器配置多个节点以避免中断。

另一个漂亮的工具是跨平台的原生模块node-memwatch,它在检测到运行代码中的内存泄漏时触发一个事件。

const memwatch = require("memwatch");

memwatch.on("leak", function (info) {  // event emitted  console.log(info.reason);
});复制代码
Copier après la connexion

事件通过leak触发,并且它的回调对象中有一个reason会随着连续垃圾回收的堆增长而增长。

使用 AppSignal 的 Magic Dashboard 诊断内存限制

AppSignal 有一个神奇的仪表板,用于监控堆增长的垃圾收集统计信息

Explorez lallocation de mémoire tas dans Node et parlez des limites de mémoire !

上图显示请求在 14:25 左右停止了 7 分钟,允许垃圾回收以减少内存压力。当对象在旧的空间中停留太久并导致内存泄漏时,仪表板也会暴露出来。

总结:解决 Node.js 内存限制和泄漏

在这篇文章中,我们首先了解了 V8 垃圾回收器的作用,然后再探讨堆内存是否存在限制以及如何扩展内存分配限制。

最后,我们使用了一些潜在的工具来密切关注 Node.js 中的内存泄漏。我们看到内存分配的监控可以通过使用一些粗略的工具方法来实现,比如memoryUsage一些调试方法。在这里,分析仍然是手动实现的。

另一种选择是使用 AppSignal 等专业工具,它提供监控、警报和漂亮的可视化来实时诊断内存问题。

希望你喜欢这篇关于内存限制和诊断内存泄漏的快速介绍。

更多node相关知识,请访问: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!

Étiquettes associées:
source:juejin.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal