ホームページ > 記事 > ウェブフロントエンド > NodeJsのメモリリーク問題の詳しい説明
以前、NODE_ENV !=production で React がサーバー上でレンダリングされているときに、メモリ リークが発生することを偶然発見しました。特定の問題: https://github.com/facebook/react/issues/7406。ノード、リアクト同型性およびその他のテクノロジの普及に伴い、ノード側のメモリ リークなどの問題に注目が集まるはずです。ノードがメモリ リークを起こしやすい理由と、メモリ リークが発生した後のトラブルシューティング方法を以下に簡単に説明し、例を示します。
まず、ノードは v8 エンジンに基づいており、そのメモリ管理方法は v8 と一致しています。以下に、v8 に関連するメモリ効果を簡単に紹介します。
V8 のメモリ制限
node は V8 に基づいて構築されており、V8 を通じて js オブジェクトの割り当てと管理を行います。 V8 にはメモリの使用に制限があります (旧世代のメモリは 64 ビット システムで約 1.4G、32 ビット システムでは約 0.7G、64 ビット システムの新世代メモリは約 32MB、32 ビット システムでは約 0.7G) 16MB)。このような制限の下では、大きなメモリ オブジェクトを操作できません。誤ってこの制限に達すると、プロセスは終了します。
理由: V8 はガベージ コレクションの実行時に JavaScript アプリケーション ロジックをブロックし、ガベージ コレクションが完了するまで JavaScript アプリケーション ロジックを再実行します。この動作は「stop-the-world」と呼ばれます。 V8 のヒープ メモリが 1.5GB の場合、V8 が小規模なガベージ コレクションを実行するには 50 ミリ秒以上かかり、非増分ガベージ コレクションを実行するには 1 秒以上かかります。
ノード --max-old-space-size=xxx (単位 MB)、ノード --max-new-space-size=xxx (単位 KB) を通じて新世代のメモリと古い世代のメモリを設定し、デフォルトのメモリを破ります限界。
V8 ヒープの構成
V8 ヒープは、実際には古い世代と新しい世代だけで構成されているわけではありません。
新世代のメモリ領域: ほとんどのオブジェクトがここに割り当てられています。この領域は小さいですが、ガベージ コレクションは特に頻繁に行われます。
旧世代のポインター領域: この領域には、他のオブジェクトへのポインターを持つ可能性のあるほとんどのオブジェクトが含まれます。
旧世代のデータ領域:古い世代に属し、元のデータ オブジェクトのみがここに保存されます。これらのオブジェクトには他のオブジェクトへのポインターがありません。
サイズが他の領域のサイズを超えるオブジェクトが保存される場所です。独自のメモリ。ガベージ コレクションは大きなオブジェクトを移動しません。
コード領域: コード オブジェクト、つまり JIT 後の命令を含むオブジェクトがここに割り当てられます。実行許可を持つ唯一のメモリ領域
Cell領域、属性Cell領域、Map領域:Cell、属性Cell、Mapを格納する各領域は同じサイズの要素を格納し、単純な構造を持っています
GCリサイクル型
式 GC
は、ガベージ コレクターがメモリ空間をスキャンするときにガベージを収集 (増加) し、スキャン サイクルの終了時にガベージを空にするかどうかを示します。
非増分 GC
非増分ガベージ コレクターを使用する場合、ガベージは収集されるとすぐに空になります。
ガベージコレクタは、新世代のメモリ領域、旧世代のポインタ領域、旧世代のデータ領域に対してのみガベージコレクションを実行します。オブジェクトはまず、占有スペースの少ない新世代のメモリに格納されます。ほとんどのオブジェクトはすぐに期限切れになるため、非増分 GC はこれらの少量のメモリを直接再利用します。一部のオブジェクトが一定期間内にリサイクルできない場合、それらは古い世代のメモリ領域に移動されます。この領域では、インクリメンタル GC が頻繁に実行されず、時間がかかります。
では、メモリリークはいつ発生するのでしょうか?
メモリリークの経路
メモリリーク
キャッシュ
キューの消費がタイムリーではない
スコープが解放されていない
Nodeのメモリ構成は主にV8を通じて割り当てられた部分とNode自体によって割り当てられた部分です。 V8 のガベージ コレクションの主な制限は、V8 のヒープ メモリです。メモリ リークの主な理由: 1. キャッシュ、2. キューの消費がタイムリーではない、3. スコープが解放されていない。 : プロセスの常駐メモリ部分
os.freemem()
合計システム メモリを返し、アイドルメモリガベージコレクションログを表示node --trace_gc -e "var a = []; for( var i = 0; i gc.log //ガベージコレクションログを出力しますnode --prof //ノードの実行パフォーマンスログを出力します。 表示するには、windows-tick.processor を使用します。
分析および監視ツール
v8-profilerは、v8ヒープメモリのスナップショットをキャプチャし、CPUを分析します
node-heapdumpは、v8ヒープメモリのスナップショットをキャプチャしますnode-mtraceはスタック使用量を分析します
node-memwatchはガベージコレクションを監視します
node- memwatch
process.memoryUsage(); { ress: 47038464, heapTotal: 34264656, heapUsed: 2052866 }
stats イベント: フルヒープ ガベージ コレクションが実行されるたびに、stats イベントがトリガーされます。このイベントはメモリ統計を提供します。
memwatch.on('stats',function(info){ console.log(info) }) memwatch.on('leak',function(info){ console.log(info) })
りー
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('<h1>Hello world</h1>'); }); setInterval(function(){ heapdump.writeSnapshot('./' + Date.now() + '.heapsnapshot'); }, 3000); http.listen(3000, function(){ console.log('listening on port 3000'); });
这里我们通过设置一个不断增加且不回被回收的数组,来模拟内存泄漏。
通过使用heap-dump模块来定时纪录内存快照,并通过chrome开发者工具profiles来导入快照,对比分析。
我们可以看到,在浏览器访问 localhost:3000 ,并多次刷新后,快照的大小一直在增长,且即使不请求,也没有减小,说明已经发生了泄漏。
接着我们通过过chrome开发者工具profiles, 导入快照。通过设置comparison,对比初始快照,发送请求,平稳,再发送请求这3个阶段的内存快照。可以发现右侧new中LeakClass一直增加。在delta中始终为正数,说明并没有被回收。
小结
针对内存泄漏可以采用植入memwatch,或者定时上报process.memoryUsage内存使用率到monitor,并设置告警阀值进行监控。
当发现内存泄漏问题时,若允许情况下,可以在本地运行node-heapdump,使用定时生成内存快照。并把快照通过chrome Profiles分析泄漏原因。若无法本地调试,在测试服务器上使用v8-profiler输出内存快照比较分析json(需要代码侵入)。
需要考虑在什么情况下开启memwatch/heapdump。考虑heapdump的频度以免耗尽了CPU。 也可以考虑其他的方式来检测内存的增长,比如直接监控process.memoryUsage()。
当心误判,短暂的内存使用峰值表现得很像是内存泄漏。如果你的app突然要占用大量的CPU和内存,处理时间可能会跨越数个垃圾回收周期,那样的话memwatch很有可能将之误判为内存泄漏。但是,这种情况下,一旦你的app使用完这些资源,内存消耗就会降回正常的水平。所以需要注意的是持续报告的内存泄漏,而可以忽略一两次突发的警报。
更多NodeJsのメモリリーク問題の詳しい説明相关文章请关注PHP中文网!