29

ネストされた関数の node.js パターンが v8 のガベージ コレクターでどのように機能するかについて興味がありました。ここに簡単な例があります

readfile("blah", function(str) {
   var val = getvaluefromstr(str);
   function restofprogram(val2) { ... } (val)
})

restof-program が長時間実行されている場合、それは str がガベージコレクションを取得しないことを意味しませんか? 私の理解では、ノードを使用すると、ネストされた関数が多くなるということです。restofprogram が外部で宣言されている場合、これはガベージコレクションを取得するため、str はスコープ内にありませんか? これは推奨される方法ですか?

編集問題を複雑にするつもりはありませんでした。それはただの不注意だったので、修正しました。

4

3 に答える 3

70

簡単な答え: の値がstrどこからも参照されていない場合 (およびstrそれ自体が から参​​照されていないrestofprogram場合)、 が戻るとすぐに到達不能になりfunction (str) { ... }ます。

詳細: V8 コンパイラは、実際のローカル変数を、 withステートメントまたは呼び出しによってシャドウされる、クロージャによってキャプチャされた、いわゆるコンテキスト変数と区別します。 eval

ローカル変数はスタック上に存在し、関数の実行が完了するとすぐに消えます。

コンテキスト変数は、ヒープに割り当てられたコンテキスト構造に存在します。コンテキスト構造が死ぬと、それらは消えます。ここで注意すべき重要なことは、同じスコープのコンテキスト変数は同じ構造に存在するということです。サンプルコードで説明しましょう:

function outer () {
  var x; // real local variable
  var y; // context variable, referenced by inner1
  var z; // context variable, referenced by inner2

  function inner1 () {
    // references context 
    use(y);
  }

  function inner2 () {
    // references context 
    use(z);
  }

  function inner3 () { /* I am empty but I still capture context implicitly */ } 

  return [inner1, inner2, inner3];
}

この例では、 variable は返さxれるとすぐに消えouterます but 変数yとは、の両方が死ぬz場合にのみ消えます。これは、とが同じコンテキスト構造に割り当てられており、3 つのクロージャーすべてがこのコンテキスト構造を暗黙的に参照している (明示的に使用していない場合でも) ために発生します。 inner1inner2 inner3yzinner3

with -statement、try/catch -statement の使用を開始すると、状況はさらに複雑になります。V8 では、 catch 句または global 内に暗黙的なwitheval -statement が含まれています。

function complication () {
  var x; // context variable

  function inner () { /* I am empty but I still capture context implicitly */ }

  try { } catch (e) { /* contains implicit with-statement */ }

  return inner;
}

この例では死亡x時にのみ消えます。innerなぜなら:

  • try/catch -catch 節に暗黙的with -statement を含む
  • V8 は、with -statementがすべてのローカルをシャドウすることを前提としています

これにより、強制x的にコンテキスト変数になり、コンテキストinnerがキャプチャされるため、死ぬxまで存在しinnerます。

一般に、特定の変数が実際に必要以上に長くオブジェクトを保持しないようにしたい場合は、その変数に代入することで、このリンクを簡単に破棄nullできます。

于 2011-03-16T16:53:03.583 に答える
4

実際、あなたの例はややトリッキーです。わざとでしたか?実際に使用するのではなく、内側のレキシカルスコープの restofprogram() の引数で外側の変数をマスクしているようです。しかし、とにかく、あなたは尋ねているので、単純にするために、あなたの例のトリッキーさを無視させてください.valvalstrval

私の推測ではstr、restofprogram() 関数が使用されなくても、関数が終了する前に変数が収集されないということです。restofprogram () が使用str されておらず、使用さeval()new Function()ていない場合は、安全に収集できますが、そうなるとは思えません。これは、V8 にとってはトリッキーな最適化であり、おそらく問題に値するものではありません。eval言語にandがない場合はnew Function()、はるかに簡単になります。

シングル スレッドのイベント ループ内のイベント ハンドラーはほぼ瞬時に終了する必要があるため、収集されないという意味である必要はありません。そうしないと、プロセス全体がブロックされ、メモリ内の1つの役に立たない変数よりも大きな問題が発生します。

あなたが実際にあなたの例に書いたこと以外のことを意味していたのだろうか. Node のプログラム全体は、ブラウザーの場合と同じです。メイン プログラム本体が既に終了した後で、非同期に発生するイベント コールバックを登録するだけです。また、どのハンドラもブロックしていないため、実際に終了するのに顕著な時間がかかる関数はありません。あなたの質問であなたが実際に何を意味したかを理解したかどうかはわかりませんが、私が書いたことがすべてがどのように機能するかを理解するのに役立つことを願っています.

アップデート:

あなたのプログラムがどのように見えるかについてのコメントでより多くの情報を読んだ後、私はもっと言うことができます。

プログラムが次のようなものである場合:

readfile("blah", function (str) {
  var val = getvaluefromstr(str);
  // do something with val
  Server.start(function (request) {
    // do something
  });
});

次に、次のように書くこともできます。

readfile("blah", function (str) {
  var val = getvaluefromstr(str);
  // do something with val
  Server.start(serverCallback);
});
function serverCallback(request) {
  // do something
});

Server.start str() が呼び出された後に範囲外になり、最終的に収集されます。また、インデントがより管理しやすくなり、より複雑なプログラムでは過小評価されません。

に関しては、valこの場合、コードを大幅に簡素化するグローバル変数にすることができます。もちろん、そうする必要はありません。クロージャーと格闘することもできますが、この場合、valグローバルにするか、readfile コールバックと serverCallback 関数の両方に共通の外部スコープでライブにすることが、最も簡単な解決策のように思えます。

匿名関数を使用できる場合はどこでも名前付き関数も使用できることを覚えておいてください。また、名前付き関数を使用すると、それらをどのスコープで使用するかを選択できます。

于 2011-03-16T14:38:51.193 に答える
1

私の推測では、str は restofprogram() で使用できるため、ガベージ コレクションは行われません。はい。また、restofprogram が外部で宣言されている場合、str は GCed を取得する必要があります。ただし、次のような場合を除きます。

function restofprogram(val) { ... }

readfile("blah", function(str) {
  var val = getvaluefromstr(str);
  restofprogram(val, str);
});

または、getvaluefromstr が次のように宣言されている場合:

function getvaluefromstr(str) {
  return {
    orig: str, 
    some_funky_stuff: 23
  };
}

フォローアップの質問: v8 は単純な GC を実行しますか、それとも GC と ref の組み合わせを実行しますか? カウント(pythonのような?)

于 2011-03-16T14:02:16.523 に答える