次の 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();
_
対応するスクリーンショット:
クロージャ(この場合、function() {}
)は、同じコンテキスト内の他のクロージャによってオブジェクトが参照されている場合、そのクロージャ自体が到達可能かどうかに関係なく、すべてのオブジェクトを「生きた」状態に保ちます。
私の質問は、他のブラウザー(IE 9+およびFirefox)でのクロージャーのガベージコレクションです。 JavaScriptヒーププロファイラーなどのWebkitのツールにはかなり精通していますが、他のブラウザーのツールはほとんど知らないので、これをテストできませんでした。
これらの3つのケースのうち、IE9 +とFirefoxがガベージコレクションするのはどれですかsomeClass
インスタンス?
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を期待していたように動作します。
私が知る限り、これはバグではなく、予想される動作です。
Mozillaの メモリ管理ページから :「2012年現在、すべての最新ブラウザーにはマークアンドスイープガベージコレクターが付属しています。」 "制限:オブジェクトは明示的に到達不能にする必要がある"。
失敗した例では、some
はまだクロージャ内で到達可能です。私はそれを到達不能にするための2つの方法を試しましたが、両方とも機能しました。不要になったときにsome=null
を設定するか、window.f_ = null;
を設定すると消えます。
更新
私は、Chrome 30、FF25、Opera 12およびWindows上のIE10で試しました。
standard はガベージコレクションについて何も述べていませんが、何が起こるべきかについての手がかりを与えます。
「(内部)語彙環境の外部参照は、内部語彙環境を論理的に囲む語彙環境への参照です。
外側の語彙環境には、もちろん、独自の外側の語彙環境があります。語彙環境は、複数の内部語彙環境の外部環境として機能する場合があります。たとえば、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_
に別の値を割り当てることにより、オブジェクトへの参照が壊れた場合、メモリは解放されます。
私の意見では、これはバグではありません。
ヒューリスティックは異なりますが、この種のことを実装する一般的な方法は、f()
の呼び出しごとに環境レコードを作成し、実際に閉じているf
のローカルのみを保存することですその環境レコードで(some閉鎖によって)オーバー。その後、f
の呼び出しで作成されたクロージャは、環境レコードを保持し続けます。これがFirefoxが少なくともクロージャーを実装する方法だと思います。
これには、クローズドオーバー変数への高速アクセスと実装の簡素化という利点があります。観察された効果の欠点があります。ある変数を閉じる短命のクロージャーが、長命のクロージャーによってそれを生かし続けます。
実際に何をクローズするかに応じて、異なるクロージャーの複数の環境レコードを作成しようとすることができますが、非常に複雑になり、パフォーマンスとメモリの問題を引き起こす可能性があります...
(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
}());
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));
}());
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