La maîtrise de l'API Node.js peut vous permettre d'avancer rapidement, mais une compréhension approfondie de l'empreinte mémoire des programmes Node.js peut vous amener plus loin.
Commençons par jeter un coup d'œil à notre utilisation de la mémoire avec process.memoryUsage(), en mettant à jour toutes les secondes :
setInterval(() => { console.log('Memory Usage:', process.memoryUsage()); }, 1000);
Étant donné que la sortie est en octets, elle n'est pas conviviale. Améliorons-le en formatant l'utilisation de la mémoire en Mo :
function formatMemoryUsageInMB(memUsage) { return { rss: convertToMB(memUsage.rss), heapTotal: convertToMB(memUsage.heapTotal), heapUsed: convertToMB(memUsage.heapUsed), external: convertToMB(memUsage.external) }; } const convertToMB = value => { return (value / 1024 / 1024).toFixed(2) + ' MB'; }; const logInterval = setInterval(() => { const memoryUsageMB = formatMemoryUsageInMB(process.memoryUsage()); console.log(`Memory Usage (MB):`, memoryUsageMB); }, 1000);
Maintenant, nous pouvons obtenir le résultat suivant chaque seconde :
Memory Usage (MB): { rss: '30.96 MB', // The actual OS memory used by the entire program, including code, data, shared libraries, etc. heapTotal: '6.13 MB', // The memory area occupied by JS objects, arrays, etc., dynamically allocated by Node.js // V8 divides the heap into young and old generations for different garbage collection strategies heapUsed: '5.17 MB', external: '0.39 MB' } Memory Usage (MB): { rss: '31.36 MB', heapTotal: '6.13 MB', heapUsed: '5.23 MB', external: '0.41 MB' }
Nous savons tous que l'utilisation de la mémoire du moteur V8 est limitée, non seulement par les politiques de gestion de la mémoire et d'allocation des ressources du système d'exploitation, mais également par ses propres paramètres.
En utilisant os.freemem(), nous pouvons voir la quantité de mémoire libre dont dispose le système d'exploitation, mais cela ne signifie pas que tout est à gagner par un programme Node.js.
console.log('Free memory:', os.freemem());
Pour les systèmes 64 bits, la taille maximale par défaut de l'ancien espace de Node.js V8 est d'environ 1,4 Go. Cela signifie que même si votre système d'exploitation dispose de plus de mémoire disponible, le V8 n'utilisera pas automatiquement plus que cette limite.
Astuce : Cette limite peut être modifiée en définissant des variables d'environnement ou en spécifiant des paramètres lors du démarrage de Node.js. Par exemple, si vous souhaitez que V8 utilise un tas plus grand, vous pouvez utiliser l'option --max-old-space-size :
node --max-old-space-size=4096 your_script.js
Cette valeur doit être définie en fonction de votre situation et de votre scénario réels. Par exemple, si vous disposez d'une machine avec beaucoup de mémoire, déployée de manière autonome, et que vous disposez de nombreuses machines à petite mémoire déployées de manière distribuée, le paramètre de cette valeur sera définitivement différent.
Exécutons un test en remplissant un tableau de données indéfiniment jusqu'à ce que la mémoire déborde et voyons quand cela se produit.
const array = []; while (true) { for (let i = 0; i < 100000; i++) { array.push(i); } const memoryUsageMB = formatMemoryUsageInMB(process.memoryUsage()); console.log(`Memory Usage (MB):`, memoryUsageMB); }
C'est ce que nous obtenons lorsque nous exécutons le programme directement. Après avoir ajouté un peu de données, le programme plante.
Memory Usage (MB): { rss: '2283.64 MB', heapTotal: '2279.48 MB', heapUsed: '2248.73 MB', external: '0.40 MB' } Memory Usage (MB): { rss: '2283.64 MB', heapTotal: '2279.48 MB', heapUsed: '2248.74 MB', external: '0.40 MB' } # # Fatal error in , line 0 # Fatal JavaScript invalid size error 169220804 # # # #FailureMessage Object: 0x7ff7b0ef8070
Vous êtes confus ? La limite n'est-elle pas 1,4G ? Pourquoi utilise-t-il la 2G ? En fait, la limite de 1,4 Go de Node.js est une limite historique du moteur V8, applicable aux premières versions du V8 et à certaines configurations. Dans Node.js et V8 modernes, Node.js ajuste automatiquement son utilisation de la mémoire en fonction des ressources système. Dans certains cas, il peut utiliser bien plus de 1,4 Go, en particulier lorsqu'il s'agit de grands ensembles de données ou d'opérations gourmandes en mémoire.
Lorsque nous définissons la limite de mémoire à 512 Mo, elle déborde lorsque le RSS atteint environ 996 Mo.
Memory Usage (MB): { rss: '996.22 MB', heapTotal: '993.22 MB', heapUsed: '962.08 MB', external: '0.40 MB' } Memory Usage (MB): { rss: '996.23 MB', heapTotal: '993.22 MB', heapUsed: '962.09 MB', external: '0.40 MB' } <--- Last few GCs ---> [22540:0x7fd27684d000] 1680 ms: Mark-sweep 643.0 (674.4) -> 386.8 (419.4) MB, 172.2 / 0.0 ms (average mu = 0.708, current mu = 0.668) allocation failure; scavenge might not succeed [22540:0x7fd27684d000] 2448 ms: Mark-sweep 962.1 (993.2) -> 578.1 (610.7) MB, 240.7 / 0.0 ms (average mu = 0.695, current mu = 0.687) allocation failure; scavenge might not succeed <--- JS stacktrace ---> FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
En résumé, pour être plus précis, la limite de mémoire de Node.js fait référence à la limite de mémoire du tas, qui est la mémoire maximale pouvant être occupée par les objets JS, les tableaux, etc., alloués par la V8.
La taille de la mémoire du tas détermine-t-elle la quantité de mémoire qu'un processus Node.js peut occuper ? Non! Continuez à lire.
Nous avons vu lors du test que la baie ne peut contenir qu'un peu plus de 2 Go avant que le programme ne plante. Donc, si j'ai un fichier de 3 Go, ne puis-je pas le mettre d'un seul coup dans la mémoire Node.js ?
Vous pouvez !
Nous avons vu une mémoire externe via process.memoryUsage(), qui est occupée par le processus Node.js mais non allouée par la V8. Tant que vous y placez le fichier de 3 Go, il n'y a pas de limite de mémoire. Comment? Vous pouvez utiliser Buffer. Buffer est un module d'extension C de Node.js qui alloue de la mémoire à l'aide d'objets et de données C, et non JS.
Voici une démo :
setInterval(() => { console.log('Memory Usage:', process.memoryUsage()); }, 1000);
Même si vous allouez 3 Go de mémoire, notre programme fonctionne toujours correctement et notre programme Node.js a occupé plus de 5 Go de mémoire car cette mémoire externe n'est pas limitée par Node.js mais par la limite du système d'exploitation sur la mémoire allouée. aux threads (vous ne pouvez donc pas vous déchaîner, même Buffer peut manquer de mémoire ; l'essence est de gérer des données volumineuses avec Streams).
Dans Node.js, le cycle de vie d'un objet Buffer est lié à un objet JavaScript. Lorsque la référence JavaScript à un objet Buffer est supprimée, le garbage collector V8 marque l'objet comme recyclable, mais la mémoire sous-jacente de l'objet Buffer n'est pas immédiatement libérée. Généralement, lorsque le destructeur de l'extension C est appelé (par exemple, lors du processus de garbage collection dans Node.js), cette partie de la mémoire est libérée. Cependant, ce processus peut ne pas être complètement synchronisé avec le garbage collection de V8.
function formatMemoryUsageInMB(memUsage) { return { rss: convertToMB(memUsage.rss), heapTotal: convertToMB(memUsage.heapTotal), heapUsed: convertToMB(memUsage.heapUsed), external: convertToMB(memUsage.external) }; } const convertToMB = value => { return (value / 1024 / 1024).toFixed(2) + ' MB'; }; const logInterval = setInterval(() => { const memoryUsageMB = formatMemoryUsageInMB(process.memoryUsage()); console.log(`Memory Usage (MB):`, memoryUsageMB); }, 1000);
En résumé : l'utilisation de la mémoire Node.js comprend l'utilisation de la mémoire du tas JS (déterminée par le garbage collection de V8) et l'allocation de mémoire par C
La stratégie générationnelle de garbage collection est très répandue dans les implémentations de langages de programmation modernes ! Des stratégies similaires telles que Generational Garbage Collection peuvent être trouvées dans Ruby, .NET et Java. Lorsque le garbage collection a lieu, cela conduit souvent à une situation « d'arrêt du monde », ce qui a inévitablement un impact sur les performances du programme. Cependant, cette conception est conçue dans un souci d'optimisation des performances.
Lorsque la mémoire est allouée, elle a lieu dans From. Lors du garbage collection, les objets actifs dans From sont inspectés et copiés vers To, suivis de la libération des objets non actifs. Lors du cycle de collecte suivant, les objets vivants de To sont répliqués vers From, auquel cas To se transforme en From et vice versa. À chaque cycle de garbage collection, From et To sont permutés. Cet algorithme réplique uniquement les objets vivants pendant le processus de copie et évite ainsi la génération de fragments de mémoire.
Alors, comment la vivacité d’une variable est-elle déterminée ? L’analyse d’accessibilité entre en jeu. Considérons les objets suivants à titre d'exemple :
Dans le cadre de l'analyse d'accessibilité :
Certes, le comptage de références peut servir de moyen auxiliaire. Néanmoins, en présence de références circulaires, il ne parvient pas à déterminer avec précision la véritable vivacité des objets.
Dans la mémoire ancienne génération, les objets sont généralement moins actifs. Cependant, lorsque la mémoire ancienne génération devient pleine, cela déclenche le nettoyage de la mémoire ancienne génération (Major GC) via l'algorithme Mark-Sweep.
L'algorithme Mark-Sweep comprend deux phases : le marquage et le balayage. Lors de la phase de marquage, le moteur V8 parcourt tous les objets du tas et marque ceux qui sont actifs. Lors de la phase de balayage, seuls les objets non marqués sont effacés. Le mérite de cet algorithme est que la phase de balayage prend relativement moins de temps puisque la proportion d'objets morts dans l'ancienne génération est relativement faible. Cependant, son inconvénient est qu'il s'efface uniquement sans compacter, ce qui peut entraîner un espace mémoire discontinu, ce qui rend peu pratique l'allocation de mémoire pour des objets volumineux.
Cette lacune donne lieu à une fragmentation de la mémoire, nécessitant l'emploi d'un autre algorithme, Mark-Compact. Cet algorithme déplace tous les objets vivants vers une extrémité, puis éradique d'un seul coup l'espace mémoire invalide sur le côté droit de la limite, obtenant ainsi un espace mémoire disponible complet et continu. Il résout le problème de fragmentation de la mémoire qui pourrait être causé par l'algorithme Mark-Sweep, mais au prix de plus de temps nécessaire pour déplacer un grand nombre d'objets vivants.
Si vous trouvez cet article utile, merci de lui donner un coup de pouce. :D
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!