Maison  >  Article  >  interface Web  >  Explication détaillée du problème de fuite de mémoire nodeJs

Explication détaillée du problème de fuite de mémoire nodeJs

高洛峰
高洛峰original
2017-01-04 16:55:201996parcourir

J'ai découvert par hasard avant que lors du rendu de React sur le serveur, lorsque NODE_ENV != production, cela provoquerait une fuite de mémoire. Problèmes spécifiques : https://github.com/facebook/react/issues/7406. Avec l'utilisation généralisée de l'isomorphisme des nœuds et des réactions et d'autres technologies, des problèmes tels que les fuites de mémoire côté nœud devraient attirer notre attention. Pourquoi le nœud est sujet aux fuites de mémoire et comment les résoudre une fois qu'elles se produisent. Vous trouverez ci-dessous une brève introduction et un exemple pour illustrer.

Tout d'abord, node est basé sur le moteur v8, et sa méthode de gestion de la mémoire est cohérente avec la v8. Ce qui suit est une brève introduction aux effets de mémoire associés de la v8.

Limite de mémoire V8

le nœud est construit sur la base de V8 et alloue et gère les objets js via V8. V8 a des restrictions sur l'utilisation de la mémoire (la mémoire d'ancienne génération est d'environ 1,4 Go dans les systèmes 64 bits, environ 0,7 Go dans les systèmes 32 bits, la mémoire de nouvelle génération dans les systèmes 64 bits est d'environ 32 Mo et les systèmes 32 bits sont d'environ 16 Mo). Avec de telles restrictions, les objets mémoire volumineux ne peuvent pas être manipulés. Si cette limite est accidentellement touchée, le processus se terminera.

Raison : V8 bloquera la logique d'application JavaScript lors de l'exécution du garbage collection, puis réexécutera la logique d'application JavaScript jusqu'à ce que le garbage collection soit terminé. Ce comportement est appelé "arrêter le monde". Si la mémoire tas du V8 est de 1,5 Go, il faut plus de 50 ms au V8 pour effectuer un petit garbage collection, et même plus d'une seconde pour effectuer un garbage collection non incrémentiel.

Utilisez node --max-old-space-size=xxx (unité Mo), node --max-new-space-size=xxx (unité Ko) pour définir la mémoire de nouvelle génération et la mémoire d'ancienne génération pour casser la limite de mémoire par défaut.

La composition du tas du V8

Le tas du V8 n'est en fait pas seulement composé de l'ancienne génération et de la nouvelle génération. Le tas peut être divisé en plusieurs zones différentes :

Zone mémoire nouvelle génération : La plupart des objets sont alloués ici. Cette zone est petite mais le garbage collection est très fréquent

Zone de pointeur d'ancienne génération : Elle appartient à l'ancienne génération. Elle contient la plupart des pointeurs pouvant pointer vers. autres objets. Objets, la plupart des objets promus de la nouvelle génération seront déplacés ici

Zone de données d'ancienne génération : appartient à l'ancienne génération, seuls les objets de données d'origine sont enregistrés ici, ces objets n'ont pas de pointeurs vers d'autres objets.

Grande zone d'objets : c'est là que sont stockés les objets dont la taille dépasse la taille des autres zones. Chaque objet a sa propre mémoire. Les objets volumineux ne seront pas déplacés lors du garbage collection. : Objets de code, qui contiennent des instructions après JIT. L'objet sera alloué ici. La seule zone mémoire avec des autorisations d'exécution

Zone de cellule, zone de cellule d'attribut, zone de carte : stocke la cellule, la cellule d'attribut et la carte. Chaque zone stocke des éléments de même taille et a une structure simple

.

Type de collecte GC

GC incrémentiel

indique si le ramasse-miettes collecte (augmente) les déchets lors de l'analyse de l'espace mémoire et vide les déchets à la fin du cycle d'analyse.

GC non incrémentiel

Lors de l'utilisation d'un garbage collector non incrémental, les garbage sont vidés dès qu'ils sont collectés.

Le garbage collector effectuera uniquement le garbage collection sur la zone mémoire de nouvelle génération, la zone de pointeur d'ancienne génération et la zone de données d'ancienne génération. Les objets entrent d'abord dans la mémoire de nouvelle génération qui prend moins de place. La plupart des objets expireront rapidement et le GC non incrémentiel récupère directement ces petites quantités de mémoire. Si certains objets ne peuvent pas être recyclés dans un délai donné, ils seront déplacés vers la zone mémoire d'ancienne génération. Cette zone effectue des GC incrémentielles peu fréquentes et prend beaucoup de temps.

Quand une fuite de mémoire se produira-t-elle ?

Chemins des fuites de mémoire

Fuites de mémoire

Cache

La consommation de la file d'attente n'est pas opportune

La portée n'est pas libérée

La composition de la mémoire de Node est principalement la partie allouée via V8 et la partie allouée par Node lui-même. La principale limitation du garbage collection du V8 est la mémoire tas du V8. Les principales raisons des fuites de mémoire : 1. Cache ; 2. La consommation de la file d'attente n'est pas opportune ; 3. La portée n'est pas publiée

Analyse des fuites de mémoire

Afficher l'utilisation de la mémoire V8 (octet unitaire) )

process.memoryUsage();
  {
    ress: 47038464, 
    heapTotal: 34264656, 
    heapUsed: 2052866 
  }

ress : la partie mémoire résidente du processus

heapTotal, tasUtilisé : informations sur la mémoire du tas V8

Vérifiez l'utilisation de la mémoire système (octet unitaire)

os.totalmem()

os.freemem()


Renvoyer la mémoire système totale et la mémoire inactive

Afficher le journal de récupération de place

node --trace_gc -e "var a = []; for( var i = 0; i > gc.log //Journal de récupération de place en sortie

node --prof //Journal des performances d'exécution du nœud de sortie. Utilisez windows-tick.processor pour afficher.

Outils d'analyse et de surveillance

v8-profiler capture des instantanés de la mémoire du tas v8 et analyse le processeur

node-heapdump capture des instantanés de la mémoire du tas v8

analyses node -mtrace la pile en utilisant

node-memwatch pour surveiller le garbage collection


node-memwatch

memwatch.on('stats',function(info){
  console.log(info)
})
memwatch.on('leak',function(info){
  console.log(info)
})

événement de statistiques : Chaque Chaque fois qu'un garbage collection complet du tas est effectué, un événement stats sera déclenché. Cet événement fournira des statistiques de mémoire.

{
"num_full_gc": 17, //第几次全栈垃圾回收
"num_inc_gc": 8,  //第几次增量垃圾回收
"heap_compactions": 8, //第几次对老生代进行整理
"estimated_base": 2592568, //预估基数
"current_base": 2592568, //当前基数
"min": 2499912, //最小
"max": 2592568, //最大
"usage_trend": 0 //使用趋势
  }

Observez num_full_gc et num_inc_gc pour refléter la situation du ramassage des ordures.

Événement de fuite : Si la mémoire n'est toujours pas libérée après 5 garbage collection consécutives, cela signifie qu'une fuite de mémoire se produit. A ce moment, un événement de fuite sera déclenché.

{ start: Fri, 29 Jun 2012 14:12:13 GMT,
end: Fri, 29 Jun 2012 14:12:33 GMT,
growth: 67984,
reason: 'heap growth over 5 consecutive GCs (20s) - 11.67 mb/hr'
}

Heap Diffing 堆内存比较 排查内存溢出代码。
下面,我们通过一个例子来演示如何排查定位内存泄漏:

首先我们创建一个导致内存泄漏的例子:

//app.js
var app = require('express')();
var http = require('http').Server(app);
var heapdump = require('heapdump');
 
var leakobjs = [];
function LeakClass(){
  this.x = 1;
}
 
app.get('/', function(req, res){
  console.log('get /');
  for(var i = 0; i < 1000; i++){
    leakobjs.push(new LeakClass());
  }
  res.send(&#39;<h1>Hello world</h1>&#39;);
});
 
setInterval(function(){
  heapdump.writeSnapshot(&#39;./&#39; + Date.now() + &#39;.heapsnapshot&#39;);
}, 3000);
 
http.listen(3000, function(){
  console.log(&#39;listening on port 3000&#39;);
});

   

这里我们通过设置一个不断增加且不回被回收的数组,来模拟内存泄漏。

通过使用heap-dump模块来定时纪录内存快照,并通过chrome开发者工具profiles来导入快照,对比分析。

我们可以看到,在浏览器访问 localhost:3000 ,并多次刷新后,快照的大小一直在增长,且即使不请求,也没有减小,说明已经发生了泄漏。

Explication détaillée du problème de fuite de mémoire nodeJs

接着我们通过过chrome开发者工具profiles, 导入快照。通过设置comparison,对比初始快照,发送请求,平稳,再发送请求这3个阶段的内存快照。可以发现右侧new中LeakClass一直增加。在delta中始终为正数,说明并没有被回收。

Explication détaillée du problème de fuite de mémoire nodeJs

小结

针对内存泄漏可以采用植入memwatch,或者定时上报process.memoryUsage内存使用率到monitor,并设置告警阀值进行监控。

当发现内存泄漏问题时,若允许情况下,可以在本地运行node-heapdump,使用定时生成内存快照。并把快照通过chrome Profiles分析泄漏原因。若无法本地调试,在测试服务器上使用v8-profiler输出内存快照比较分析json(需要代码侵入)。

需要考虑在什么情况下开启memwatch/heapdump。考虑heapdump的频度以免耗尽了CPU。 也可以考虑其他的方式来检测内存的增长,比如直接监控process.memoryUsage()。

当心误判,短暂的内存使用峰值表现得很像是内存泄漏。如果你的app突然要占用大量的CPU和内存,处理时间可能会跨越数个垃圾回收周期,那样的话memwatch很有可能将之误判为内存泄漏。但是,这种情况下,一旦你的app使用完这些资源,内存消耗就会降回正常的水平。所以需要注意的是持续报告的内存泄漏,而可以忽略一两次突发的警报。

更多Explication détaillée du problème de fuite de mémoire nodeJs相关文章请关注PHP中文网!


Déclaration:
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