web-dev-qa-db-ja.com

JavaScriptのメモリリークとクロージャ-いつ、なぜですか?

クロージャーを使用するとJavaScriptのメモリリークが大量に発生することをWebでよく読んでいます。ほとんどの場合、これらの記事はスクリプトコードとDOMイベントの混合について言及しています。スクリプトはDOMを指し、その逆も同様です。

閉鎖が問題になる可能性があることを理解しています。

しかし、Node.jsはどうですか?ここでは、当然DOMがありません。したがって、ブラウザのようにメモリリークの副作用が発生する可能性はありません。

クロージャーには他にどのような問題がありますか?誰もこれについて詳しく説明したり、これに関する良いチュートリアルを教えてくれたりできますか?

この質問は、ブラウザではなくNode.jsを明示的に対象としていることに注意してください。

50
Golo Roden

この質問 似たようなことを尋ねます。基本的には、コールバックでクロージャを使用する場合、終了時にコールバックを「サブスクライブ解除」する必要があるため、GCは再度コールできないことを認識します。これは私にとって理にかなっています。呼び出されるのを待っているだけのクロージャーがある場合、GCはそれが終了したことを知るのに苦労します。クロージャをコールバックメカニズムから手動で削除することで、参照されずに収集できるようになります。

また、Mozillaは Node.jsでのメモリリークの発見に関する素晴らしい記事 コードを公開しています。それらの戦略のいくつかを試してみると、漏れのある動作を表現するコードの部分を見つけることができると思います。ベストプラクティスはすばらしく、すべてですが、プログラムのニーズを理解し、経験に基づいて観察できる内容に基づいてパーソナライズされたベストプラクティスを考え出すことがより役立つと思います。

Mozillaの記事からの抜粋です。

  • Jimb Esserの_node-mtrace_。GCC mtraceユーティリティを使用してヒープ使用量をプロファイルします。
  • Dave Pachecoの_node-heap-dump_はV8ヒープのスナップショットを取得し、すべてを巨大なJSONファイルにシリアル化します。 JavaScriptで作成されたスナップショットをトラバースおよび調査するツールが含まれています。
  • Danny Coatesの_v8-profiler_および_node-inspector_は、V8プロファイラーにNodeバインディングを提供し、WebKit Webインスペクターを使用してNodeデバッグインターフェイスを提供します。
  • リテイナーズグラフを無効にしない同じFelix Gnassのフォーク
  • FelixGeisendörferのNode Memory Leak Tutorialは、_v8-profiler_および_node-debugger_の使用方法に関する簡潔でわかりやすい説明であり、現在、ほとんどのNodeの最新技術です。 jsメモリリークのデバッグ。
  • JoyentのSmartOSプラットフォーム。Node.jsのメモリリークをデバッグするためのツールを豊富に用意しています。

この質問 に対する答えは、基本的にnullをクロージャー変数に割り当てることでGCを助けることができると言っています。

_var closureVar = {};
doWork(function callback() {
  var data = closureVar.usefulData;
  // Do a bunch of work
  closureVar = null;
});
_

insideで宣言された変数は、関数が戻ったときに消滅します。except他の閉鎖。この例では、closureVarcallback()が呼び出されるまでメモリ内にある必要がありますが、それがいつ発生するかは誰にわかりますか?コールバックが呼び出されたら、クロージャー変数をnullに設定して、GCにヒントを与えることができます。

[〜#〜]免責事項[〜#〜]:以下のコメントからわかるように、SOユーザーがいますこの情報は古く、Node.jsにとって重要ではないと言う人。まだ決定的な答えはありません。見つけたものをウェブに投稿しています。

36

良い例と説明は、David Glasserによる このブログ投稿 にあります。

さて、ここにあります(コメントをいくつか追加しました):

var theThing = null;
var cnt = 0; // helps us to differentiate the leaked objects in the debugger
var replaceThing = function () {
    var originalThing = theThing;
    var unused = function () {
        if (originalThing) // originalThing is used in the closure and hence ends up in the lexical environment shared by all closures in that scope
            console.log("hi");
    };
    // originalThing = null; // <- nulling originalThing here tells V8 gc to collect it 
    theThing = {
        longStr: (++cnt) + '_' + (new Array(1000000).join('*')),
        someMethod: function () { // if not nulled, original thing is now attached to someMethod -> <function scope> -> Closure
            console.log(someMessage);
        }
    };
};
setInterval(replaceThing, 1000);

originalThing in Chrome開発ツール(タイムラインタブ、メモリビュー、クリックレコード))を使用して、または使用せずに試してください。上記の例はブラウザとNode.jsに適用されることに注意してください環境。

また、特に Vyacheslav Egorov に感謝します。

10
borisdiakur

メモリリークの原因であるクロージャに反対する必要があります。これは、古いバージョンのIEの粗雑なガベージコレクションのためです。メモリリークとは何かを明確に述べているDouglas Crockfordの this の記事をお読みください。

再利用されていないメモリはリークしたと言われています。

リークは問題ではなく、効率的なガベージコレクションが問題です。リークは、ブラウザとサーバーの両方のJavaScriptアプリケーションで同様に発生する可能性があります。例としてV8を取り上げます。ブラウザでは、別のウィンドウ/タブに切り替えると、tabでガベージコレクションが発生します。アイドル時にリークが詰まります。タブはアイドル状態にすることができます。

サーバー上での作業はそれほど簡単ではありません。リークは発生する可能性がありますが、GCはそれほど費用対効果が高くありません。サーバーはGCを頻繁に実行する余裕がないため、パフォーマンスが影響を受けます。ノードプロセスが特定のメモリ使用量に達すると、GCが起動します。リークは定期的に削除されます。しかし、リークは依然として高速で発生する可能性があり、プログラムがクラッシュします。

2
user568109