web-dev-qa-db-ja.com

JavaScriptクロージャーのガベージコレクションの方法

次の Chromeのバグ をログに記録しました。これにより、コード内で多くの重大かつ非自明なメモリリークが発生しました。

(これらの結果はChrome Dev Tools ' memory profiler を使用し、GCを実行し、ガベージコレクションされていないすべてのヒープスナップショットを取得します。)

以下のコードでは、someClassインスタンスがガベージコレクションされます(良好):

_var someClass = function() {};

function f() {
  var some = new someClass();
  return function() {};
}

window.f_ = f();
_

ただし、この場合はガベージコレクションされません(悪い):

_var someClass = function() {};

function f() {
  var some = new someClass();
  function unreachable() { some; }
  return function() {};
}

window.f_ = f();
_

対応するスクリーンショット:

screenshot of Chromebug

クロージャ(この場合、function() {})は、同じコンテキスト内の他のクロージャによってオブジェクトが参照されている場合、そのクロージャ自体が到達可能かどうかに関係なく、すべてのオブジェクトを「生きた」状態に保ちます。

私の質問は、他のブラウザー(IE 9+およびFirefox)でのクロージャーのガベージコレクションです。 JavaScriptヒーププロファイラーなどのWebkitのツールにはかなり精通していますが、他のブラウザーのツールはほとんど知らないので、これをテストできませんでした。

これらの3つのケースのうち、IE9 +とFirefoxがガベージコレクションするのはどれですかsomeClassインスタンス?

166
Paul Draper

IE9 +およびFirefoxでこれをテストしました。

_function f() {
  var some = [];
  while(some.length < 1e6) {
    some.Push(some.length);
  }
  function g() { some; } //removing this fixes a massive memory leak
  return function() {};   //or removing this
}

var a = [];
var interval = setInterval(function() {
  var len = a.Push(f());
  if(len >= 500) {
    clearInterval(interval);
  }
}, 10);
_

ライブサイト こちら

最小限のメモリを使用して、500個のfunction() {}の配列を作成したいと考えました。

残念ながら、そうではありませんでした。空の各関数は、100万個の(永久に到達不能ですが、GCされていない)配列を保持します。

Chromeは最終的に停止して終了し、Firefoxはほぼ4GBのRAMを使用した後にすべてを終了し、IEは「メモリ不足」を示すまで漸近的に遅くなります。

コメント行のいずれかを削除すると、すべてが修正されます。

これら3つのブラウザー(Chrome、Firefox、およびIE)はすべて、クロージャーごとではなく、コンテキストごとに環境レコードを保持しているようです。ボリスは、この決定の背後にある理由はパフォーマンスであると仮説を立てており、上記の実験に照らしてパフォーマンスがどの程度呼び出されるかはわかりませんが、そうであるようです。

代わりにsomeを参照するクロージャーが必要な場合(ここでは使用していませんが、使用したと想像してください)、代わりに

_function g() { some; }
_

私が使う

_var g = (function(some) { return function() { some; }; )(some);
_

クロージャーを他の関数とは異なるコンテキストに移動することにより、メモリの問題を修正します。

これは私の人生をより退屈なものにします。

追伸好奇心から、Java(関数内でクラスを定義する機能を使用))でこれを試しました。GCは、当初Javascriptを期待していたように動作します。

48
Paul Draper

私が知る限り、これはバグではなく、予想される動作です。

Mozillaの メモリ管理ページから :「2012年現在、すべての最新ブラウザーにはマークアンドスイープガベージコレクターが付属しています。」 "制限:オブジェクトは明示的に到達不能にする必要がある"

失敗した例では、someはまだクロージャ内で到達可能です。私はそれを到達不能にするための2つの方法を試しましたが、両方とも機能しました。不要になったときにsome=nullを設定するか、window.f_ = null;を設定すると消えます。

更新

私は、Chrome 30、FF25、Opera 12およびWindows上のIE10で試しました。

standard はガベージコレクションについて何も述べていませんが、何が起こるべきかについての手がかりを与えます。

  • セクション13関数の定義、ステップ4:「13.2で指定されているように、新しいFunctionオブジェクトを作成した結果をクロージャにする」
  • 13.2項「スコープで指定された語彙環境」(スコープ=クロージャ)
  • セクション10.2字句環境:

「(内部)語彙環境の外部参照は、内部語彙環境を論理的に囲む語彙環境への参照です。

外側の語彙環境には、もちろん、独自の外側の語彙環境があります。語彙環境は、複数の内部語彙環境の外部環境として機能する場合があります。たとえば、Function Declarationにネストされた2つのFunction Declarationsが含まれている場合、ネストされた各関数の字句環境は、外部の字句環境として現在の実行の字句環境を持ちます。周囲の機能の。」

したがって、関数は親の環境にアクセスできます。

したがって、someは、返される関数のクロージャーで使用可能になります。

次に、なぜそれが常に利用可能でないのですか?

ChromeとFFは場合によっては変数を削除するのに十分賢いようですが、両方でOperaとIEクロージャーでsome変数を使用できます(NB:これを表示するには、return nullにブレークポイントを設定し、デバッガーをチェックします)。

GCは、関数でsomeが使用されているかどうかを検出するように改善できますが、複雑になります。

悪い例:

var someClass = function() {};

function f() {
  var some = new someClass();
  return function(code) {
    console.log(eval(code));
  };
}

window.f_ = f();
window.f_('some');

上記の例では、GCは変数が使用されているかどうかを知る方法がありません(コードがテストされ、Chrome30、FF25、Opera 12およびIE10)で動作します)。

window.f_に別の値を割り当てることにより、オブジェクトへの参照が壊れた場合、メモリは解放されます。

私の意見では、これはバグではありません。

77
some

ヒューリスティックは異なりますが、この種のことを実装する一般的な方法は、f()の呼び出しごとに環境レコードを作成し、実際に閉じているfのローカルのみを保存することですその環境レコードで(some閉鎖によって)オーバー。その後、fの呼び出しで作成されたクロージャは、環境レコードを保持し続けます。これがFirefoxが少なくともクロージャーを実装する方法だと思います。

これには、クローズドオーバー変数への高速アクセスと実装の簡素化という利点があります。観察された効果の欠点があります。ある変数を閉じる短命のクロージャーが、長命のクロージャーによってそれを生かし続けます。

実際に何をクローズするかに応じて、異なるクロージャーの複数の環境レコードを作成しようとすることができますが、非常に複雑になり、パフォーマンスとメモリの問題を引き起こす可能性があります...

15
Boris Zbarsky
(function(){

   function addFn(){

    var total = 0;
        
        if(total==0){   
        return function(val){
      total += val;      
      console.log("hello:"+total);
           return total+9;
    }   
        }else{
         console.log("hey:"+total);
        }
         
  };

   var add = addFn();
   console.log(add);  
   

    var r= add(5);  //5
        console.log("r:"+r); //14 
        var r= add(20);  //25
        console.log("r:"+r); //34
        var r= add(10);  //35
        console.log("r:"+r);  //44
        
        
var addB = addFn();
         var r= addB(6);  //6
         var r= addB(4);  //10
          var r= addB(19);  //29
    
  
}());
0
Avinash Maurya
  1. 関数呼び出し間で状態を維持します。関数add()があり、複数の呼び出しで渡されたすべての値を追加して合計を返したいとします。

add(5)のような; // 5を返します

add(20); // 25(5 + 20)を返します

add(3); // 28(25 + 3)を返します

最初にこれを行う2つの方法は、通常はグローバル変数を定義することです。もちろん、合計を保持するためにグローバル変数を使用できます。ただし、グローバルを(ab)使用すると、この人物が生きてしまうことに注意してください。

現在、最新の方法クロージャを使用 out define グローバル変数

(function(){

  var addFn = function addFn(){

    var total = 0;
    return function(val){
      total += val;
      return total;
    }

  };

  var add = addFn();

  console.log(add(5));
  console.log(add(20));
  console.log(add(3));
  
}());
0
Avinash Maurya
function Country(){
    console.log("makesure country call");       
   return function State(){
   
    var totalstate = 0; 
        
        if(totalstate==0){      
        
        console.log("makesure statecall");      
        return function(val){
      totalstate += val;         
      console.log("hello:"+totalstate);
           return totalstate;
    }   
        }else{
         console.log("hey:"+totalstate);
        }
         
  };  
};

var CA=Country();
 
 var ST=CA();
 ST(5); //we have add 5 state
 ST(6); //after few year we requare  have add new 6 state so total now 11
 ST(4);  // 15
 
 var CB=Country();
 var STB=CB();
 STB(5); //5
 STB(8); //13
 STB(3);  //16

 var CX=Country;
 var d=Country();
 console.log(CX);  //store as copy of country in CA
 console.log(d);  //store as return in country function in d
0
Avinash Maurya