62

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

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

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

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

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

4

3 に答える 3

44

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

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

以下は、Mozilla の記事からの簡単な抜粋です。

  • Jimb Esser の .GCCユーティリティをnode-mtrace使用してmtraceヒープ使用量をプロファイリングします。
  • Dave Pacheconode-heap-dumpは、V8 ヒープのスナップショットを取得し、すべてを巨大な JSON ファイルにシリアル化します。これには、結果のスナップショットを JavaScript でトラバースして調査するためのツールが含まれています。
  • Danny Coatesは、V8 プロファイラー用の Node バインディングと、WebKit Web Inspector を使用した Node デバッグ インターフェイスを提供しますv8-profilernode-inspector
  • Felix Gnass の fork は、retainers グラフを無効にします。
  • Felix Geisendö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;
});

関数内で宣言された変数は、他のクロージャーで使用されているものを除いて、関数が戻るときに消えます。この例でclosureVarは、 が呼び出されるまでメモリ内に存在する必要がありますがcallback()、それがいつになるかは誰にもわかりません。コールバックが呼び出されたら、クロージャー変数を null に設定することで、GC にヒントを与えることができます。

免責事項: 以下のコメントからわかるように、この情報は古く、Node.js にとって重要ではないと言う SO ユーザーがいます。それについてはまだ決定的な答えがありません。ネットで見つけたものを掲載しています。

于 2013-10-30T14:47:31.603 に答える
14

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);

originalThingChrome 開発ツール (タイムライン タブ、メモリ ビュー、クリック レコード) でヌル化の有無にかかわらず試してみてください。上記の例は、ブラウザと Node.js 環境に適用されることに注意してください。

また、特にVyacheslav Egorovの功績によるものです。

于 2014-03-03T12:29:12.477 に答える
2

閉鎖がメモリリークの原因であることに同意しなければなりません。古いバージョンの IE では、ガベージ コレクションが不十分なため、これが当てはまる可能性があります。Douglas Crockford のこの記事を読んでください。メモリ リークとは何かを明確に述べています。

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

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

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

于 2013-10-24T10:57:57.823 に答える