Heim > Web-Frontend > js-Tutorial > Hauptteil

Detaillierte Erklärung des NodeJ-Speicherleckproblems

高洛峰
Freigeben: 2017-01-04 16:55:20
Original
1972 Leute haben es durchsucht

Ich habe zuvor zufällig herausgefunden, dass beim Rendern von React auf dem Server bei NODE_ENV != Produktion ein Speicherverlust auftritt. Spezifische Probleme: https://github.com/facebook/react/issues/7406. Mit der weit verbreiteten Verwendung von Node-, React-Isomorphismus- und anderen Technologien sollten Probleme wie knotenseitige Speicherlecks unsere Aufmerksamkeit erregen. Warum Knoten anfällig für Speicherlecks sind und wie man sie nach ihrem Auftreten beheben kann, finden Sie im Folgenden eine kurze Einführung und ein Beispiel zur Veranschaulichung.

Erstens basiert Node auf der v8-Engine und seine Speicherverwaltungsmethode stimmt mit v8 überein. Im Folgenden finden Sie eine kurze Einführung in die damit verbundenen Speichereffekte von Version 8.

V8-Speicherlimit

Knoten basiert auf V8 und weist JS-Objekte über V8 zu und verwaltet sie. V8 unterliegt Einschränkungen bei der Speichernutzung (der Speicher der alten Generation beträgt etwa 1,4 GB in 64-Bit-Systemen, etwa 0,7 GB in 32-Bit-Systemen, der Speicher der neuen Generation in 64-Bit-Systemen beträgt etwa 32 MB und der Speicher der neuen Generation beträgt etwa 32 MB und der Speicher der 32-Bit-Systeme ca 16 MB). Unter solchen Einschränkungen können große Speicherobjekte nicht manipuliert werden. Wenn dieser Grenzwert versehentlich berührt wird, wird der Prozess beendet.

Grund: V8 blockiert die JavaScript-Anwendungslogik, wenn die Garbage Collection durchgeführt wird, und führt dann die JavaScript-Anwendungslogik erneut aus, bis die Garbage Collection abgeschlossen ist. Dieses Verhalten wird als „Stop-the-World“ bezeichnet. Wenn der Heap-Speicher von V8 1,5 GB beträgt, dauert es mehr als 50 ms, bis V8 eine kleine Speicherbereinigung durchführt, und sogar mehr als 1 Sekunde, um eine nicht inkrementelle Speicherbereinigung durchzuführen.

Verwenden Sie node --max-old-space-size=xxx (Einheit MB), node --max-new-space-size=xxx (Einheit KB), um den Speicher der neuen Generation und den Speicher der alten Generation festzulegen um das Standardspeicherlimit zu knacken.

Die Heap-Zusammensetzung von V8

Der Heap von V8 besteht eigentlich nicht nur aus der alten und der neuen Generation. Der Heap kann in verschiedene Bereiche unterteilt werden:

Speicherbereich der neuen Generation: Dieser Bereich ist klein, aber die Speicherbereinigung erfolgt sehr häufig.

Zeigerbereich der alten Generation: Er gehört zur alten Generation. Er enthält die meisten Zeiger, auf die verweisen kann Andere Objekte. Die meisten Objekte der neuen Generation werden hierher verschoben

Datenbereich der alten Generation: Gehört zur alten Generation, hier werden nur Originaldatenobjekte gespeichert, auf die diese Objekte nicht verweisen Andere Objekte

Großer Objektbereich: Hier werden Objekte gespeichert, deren Größe die Größe anderer Bereiche überschreitet. Große Objekte werden während der Speicherbereinigung nicht verschoben Codebereich: Codeobjekte, die Anweisungen nach JIT enthalten. Das Objekt wird hier zugewiesen. Der einzige Speicherbereich mit Ausführungsberechtigungen

Zellbereich, Attribut Zellbereich, Kartenbereich: speichert Zelle, Attribut Zelle und Karte. Jeder Bereich speichert Elemente gleicher Größe und hat eine einfache Struktur


GC-Sammeltyp

Inkrementeller GC

gibt an, ob der Garbage Collector beim Scannen des Speicherplatzes Müll sammelt (erhöht) und den Müll am Ende des Scanzyklus entleert.

Nicht inkrementelle GC

Bei Verwendung eines nicht inkrementellen Garbage Collectors wird der Müll geleert, sobald er eingesammelt wird.

Der Garbage Collector führt die Garbage Collection nur für den Speicherbereich der neuen Generation, den Zeigerbereich der alten Generation und den Datenbereich der alten Generation durch. Objekte gelangen zunächst in den Speicher der neuen Generation, der weniger Platz beansprucht. Die meisten Objekte verfallen schnell und nicht inkrementelle GC beansprucht diese kleinen Speichermengen direkt zurück. Wenn einige Objekte nicht innerhalb eines bestimmten Zeitraums recycelt werden können, werden sie in den Speicherbereich der alten Generation verschoben. Dieser Bereich führt selten inkrementelle GC durch und dauert lange.

Wann tritt ein Speicherverlust auf?

Pfade von Speicherlecks

Speicherlecks

Cache

Warteschlangenverbrauch erfolgt nicht rechtzeitig

Umfang wird nicht freigegeben

Die Speicherzusammensetzung von Node besteht hauptsächlich aus dem durch V8 zugewiesenen Teil und dem von Node selbst zugewiesenen Teil. Die Hauptbeschränkung der Garbage Collection von V8 ist der Heap-Speicher von V8. Die Hauptgründe für Speicherlecks: 1. Cache; 2. Warteschlangenverbrauch ist nicht rechtzeitig; 3. Bereich ist nicht freigegeben


Speicherleckanalyse

V8-Speichernutzung anzeigen (Einheit: Byte)

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

ress: der residente Speicherteil des Prozesses

heapTotal, heapUsed: V8-Heapspeicherinformationen

Überprüfen Sie die Systemspeichernutzung (Einheitsbyte)

os.totalmem()

os.freemem()



Geben Sie den gesamten Systemspeicher und den Leerlaufspeicher zurück

Garbage-Collection-Protokoll anzeigen

node --trace_gc -e "var a = []; for( var i = 0; i < 1000000; i++ ) { a.push(new Array(100 )); } " >> gc.log //Garbage-Collection-Protokoll ausgeben

node --prof //Knotenausführungsleistungsprotokoll ausgeben. Verwenden Sie zum Anzeigen den Windows-Tick.Prozessor.


Analyse- und Überwachungstools

v8-profiler erfasst Snapshots des v8-Heapspeichers und analysiert die CPU

node-heapdump erfasst Snapshots des v8-Heapspeichers

node -mtrace analysiert der Stapel verwendet
node-memwatch, um die Garbage Collection zu überwachen


node-memwatch

memwatch.on(&#39;stats&#39;,function(info){
  console.log(info)
})
memwatch.on(&#39;leak&#39;,function(info){
  console.log(info)
})
Nach dem Login kopieren

stats event : Each Sobald eine vollständige Heap-Garbage Collection durchgeführt wird, wird ein Statistikereignis ausgelöst. Dieses Ereignis liefert Speicherstatistiken.

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

Beobachten Sie num_full_gc und num_inc_gc, um die Situation der Speicherbereinigung widerzuspiegeln.

Leckereignis: Wenn der Speicher nach 5 aufeinanderfolgenden Garbage Collections immer noch nicht freigegeben wird, liegt ein Speicherleck vor. Zu diesem Zeitpunkt wird ein Leckereignis ausgelöst.

{ start: Fri, 29 Jun 2012 14:12:13 GMT,
end: Fri, 29 Jun 2012 14:12:33 GMT,
growth: 67984,
reason: &#39;heap growth over 5 consecutive GCs (20s) - 11.67 mb/hr&#39;
}
Nach dem Login kopieren

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

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

//app.js
var app = require(&#39;express&#39;)();
var http = require(&#39;http&#39;).Server(app);
var heapdump = require(&#39;heapdump&#39;);
 
var leakobjs = [];
function LeakClass(){
  this.x = 1;
}
 
app.get(&#39;/&#39;, function(req, res){
  console.log(&#39;get /&#39;);
  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;);
});
Nach dem Login kopieren

   

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

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

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

Detaillierte Erklärung des NodeJ-Speicherleckproblems

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

Detaillierte Erklärung des NodeJ-Speicherleckproblems

小结

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

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

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

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

更多Detaillierte Erklärung des NodeJ-Speicherleckproblems相关文章请关注PHP中文网!


Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage