ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScriptのガベージコレクションの仕組みを徹底理解_基礎知識

JavaScriptのガベージコレクションの仕組みを徹底理解_基礎知識

亚连
亚连オリジナル
2018-05-19 16:39:30950ブラウズ

ここで、JavaScript のガベージ コレクション メカニズムについて包括的に理解していただきます。今からそれを皆さんと共有し、皆さんの参考にしてください。

1. ガベージ コレクション メカニズム - GC

JavaScript には自動ガベージ コレクション メカニズム (GC: ガベージ コレクション) があり、コードの実行中に使用されるメモリは実行環境が管理することになります。

原則: ガベージコレクターは、定期的に (定期的に) 使用されなくなった変数を見つけて、そのメモリを解放します。

JavaScript ガベージ コレクションのメカニズムは非常に単純です。使用されなくなった変数を見つけて、それらが占有しているメモリを解放します。ただし、このプロセスは比較的高価であるためリアルタイムではないため、ガベージ コレクターが後続します。一定時間間隔で定期的に実行されます

使用されなくなった変数は、ライフサイクルが終了した変数です。もちろん、グローバル変数のライフサイクルは、ブラウザーがページをアンロードするまで終了しません。ローカル変数は、関数の実行中にのみ存在します。このプロセス中に、ローカル変数用の対応するスペースがスタックまたはヒープに割り当てられ、これらの変数は関数の終了およびクロージャまで関数内で使用されます。パッケージには内部機能があるため、外部機能が終わりであるとは考えられません。

コードの説明に行きましょう:

function fn1() {
 var obj = {name: 'hanzichi', age: 10};
}
 
function fn2() {
 var obj = {name:'hanzichi', age: 10};
 return obj;
}
 
var a = fn1();
var b = fn2();

コードがどのように実行されるかを見てみましょう。まず、fn1 と fn2 という 2 つの関数が定義されています。fn1 が呼び出されると、fn1 の環境に入り、オブジェクト {name: 'hanzichi', age: 10} を保存するためにメモリが開かれます。 fn1 環境が完了すると、このメモリ ブロックは js エンジンのガベージ コレクターによって自動的に解放され、fn2 が呼び出されるプロセス中に、返されたオブジェクトはグローバル変数 b によってポイントされるため、このメモリ ブロックは解放されないこと。

ここで疑問が生じます: どの変数が役に立たないのでしょうか?したがって、ガベージ コレクターは、どの変数が役に立たないかを追跡し、将来のメモリ再利用に備えて役に立たなくなった変数にマークを付ける必要があります。未使用の変数をマークするために使用される戦略は実装によって異なりますが、一般的にはマークスイープと参照カウントの 2 つの実装があります。参照カウントはあまり一般的には使用されず、マークアンドスイープがより一般的に使用されます。

2. マークとクリア

js で最も一般的に使用されるガベージ コレクション メソッドはマークとクリアです。たとえば、関数内で変数を宣言することによって変数が環境に入ると、その変数には「環境に入った」というマークが付けられます。論理的には、環境に入る変数によって占有されているメモリは、実行フローが対応する環境に入るたびに使用される可能性があるため、解放することはできません。そして、変数が環境から離れると、「環境から離れる」とマークされます。

function test(){
 var a = 10 ; //被标记 ,进入环境 
 var b = 20 ; //被标记 ,进入环境
}
test(); //执行完毕 之后 a、b又被标离开环境,被回收。

ガベージ コレクターは、実行時にメモリに保存されているすべての変数をマークします (もちろん、どのようなマーク方法でも使用できます)。次に、環境内の変数および環境内の変数によって参照される変数のタグ (クロージャ) を削除します。これ以降にマークされた変数は、環境内の変数がこれらの変数にアクセスできなくなるため、削除される変数とみなされます。最後に、ガベージ コレクターはメモリのクリーンアップ作業を完了し、マークされた値を破棄し、それらが占有しているメモリ領域を再利用します。

これまでのところ、IE、Firefox、Opera、Chrome、Safari の JS 実装はすべて、マークアンドスイープ ガベージ コレクション戦略または同様の戦略を使用していますが、ガベージ コレクションの間隔は異なります。

3. 参照カウント

参照カウントの意味は、各値が参照された回数を追跡および記録することです。変数が宣言され、その変数に参照型の値が割り当てられている場合、この値への参照の数は 1 です。同じ値が別の変数に代入されている場合、その値の参照カウントは 1 増加します。逆に、この値への参照を含む変数が別の値を取得すると、この値への参照の数は 1 つ減ります。この値への参照の数が 0 になると、この値にアクセスする方法がなくなったことを意味するため、この値が占有しているメモリ領域を再利用できます。このようにして、次回ガベージ コレクターが実行されるときに、それらの値によって占有されていたメモリが参照カウント 0 で解放されます。

function test(){
 var a = {} ; //a的引用次数为0 
 var b = a ; //a的引用次数加1,为1 
 var c =a; //a的引用次数再加1,为2
 var b ={}; //a的引用次数减1,为1
}

Netscape Navigator3 は、参照カウント戦略を使用した最初のブラウザでしたが、すぐに循環参照という深刻な問題に遭遇しました。循環参照とは、オブジェクト A にオブジェクト B へのポインターが含まれており、オブジェクト B にもオブジェクト A への参照が含まれていることを意味します。

function fn() {
 var a = {};
 var b = {};
 a.pro = b;
 b.pro = a;
}
 
fn();

上記のコードの a と b の参照時間は両方とも 2 です。 fn() が実行された後、両方のオブジェクトは環境から離れています。マーク モードとクリア モードでは問題ありませんが、参照カウント戦略では、 a と b への参照の数が 0 ではないため、fn 関数が多数呼び出される場合、メモリはガベージ コレクターによって再利用されず、メモリ リークが発生します。 IE7 と IE8 では、メモリが急増しました。

我们知道,IE中有一部分对象并不是原生js对象。例如,其内存泄露DOM和BOM中的对象就是使用C++以COM对象的形式实现的,而COM对象的垃圾回收机制采用的就是引用计数策略。因此,即使IE的js引擎采用标记清除策略来实现,但js访问的COM对象依然是基于引用计数策略的。换句话说,只要在IE中涉及COM对象,就会存在循环引用的问题。

var element = document.getElementById("some_element");
var myObject = new Object();
myObject.e = element;
element.o = myObject;

这个例子在一个DOM元素(element)与一个原生js对象(myObject)之间创建了循环引用。其中,变量myObject有一个名为element的属性指向element对象;而变量element也有一个属性名为o回指myObject。由于存在这个循环引用,即使例子中的DOM从页面中移除,它也永远不会被回收。

看上面的例子,有同学回觉得太弱了,谁会做这样无聊的事情,其实我们是不是就在做

window.onload=function outerFunction(){
 var obj = document.getElementById("element");
 obj.onclick=function innerFunction(){};
};

这段代码看起来没什么问题,但是obj引用了document.getElementById(“element”),而document.getElementById(“element”)的onclick方法会引用外部环境中德变量,自然也包括obj,是不是很隐蔽啊。

解决办法

最简单的方式就是自己手工解除循环引用,比如刚才的函数可以这样

myObject.element = null;
element.o = null;
window.onload=function outerFunction(){
 var obj = document.getElementById("element");
 obj.onclick=function innerFunction(){};
 obj=null;
};

将变量设置为null意味着切断变量与它此前引用的值之间的连接。当垃圾回收器下次运行时,就会删除这些值并回收它们占用的内存。

要注意的是,IE9+并不存在循环引用导致Dom内存泄露问题,可能是微软做了优化,或者Dom的回收方式已经改变

四、内存管理

1、什么时候触发垃圾回收?

垃圾回收器周期性运行,如果分配的内存非常多,那么回收工作也会很艰巨,确定垃圾回收时间间隔就变成了一个值得思考的问题。IE6的垃圾回收是根据内存分配量运行的,当环境中存在256个变量、4096个对象、64k的字符串任意一种情况的时候就会触发垃圾回收器工作,看起来很科学,不用按一段时间就调用一次,有时候会没必要,这样按需调用不是很好吗?但是如果环境中就是有这么多变量等一直存在,现在脚本如此复杂,很正常,那么结果就是垃圾回收器一直在工作,这样浏览器就没法儿玩儿了。

微软在IE7中做了调整,触发条件不再是固定的,而是动态修改的,初始值和IE6相同,如果垃圾回收器回收的内存分配量低于程序占用内存的15%,说明大部分内存不可被回收,设的垃圾回收触发条件过于敏感,这时候把临街条件翻倍,如果回收的内存高于85%,说明大部分内存早就该清理了,这时候把触发条件置回。这样就使垃圾回收工作职能了很多

2、合理的GC方案

1)、Javascript引擎基础GC方案是(simple GC):mark and sweep(标记清除),即:

  • (1)遍历所有可访问的对象。

  • (2)回收已不可访问的对象。

2)、GC的缺陷

和其他语言一样,javascript的GC策略也无法避免一个问题:GC时,停止响应其他操作,这是为了安全考虑。而Javascript的GC在100ms甚至以上,对一般的应用还好,但对于JS游戏,动画对连贯性要求比较高的应用,就麻烦了。这就是新引擎需要优化的点:避免GC造成的长时间停止响应。

3)、GC优化策略

David大叔主要介绍了2个优化方案,而这也是最主要的2个优化方案了:

(1)分代回收(Generation GC)

这个和Java回收策略思想是一致的。目的是通过区分“临时”与“持久”对象;多回收“临时对象”区(young generation),少回收“持久对象”区(tenured generation),减少每次需遍历的对象,从而减少每次GC的耗时。如图:

这里需要补充的是:对于tenured generation对象,有额外的开销:把它从young generation迁移到tenured generation,另外,如果被引用了,那引用的指向也需要修改。

(2)增量GC
这个方案的思想很简单,就是“每次处理一点,下次再处理一点,如此类推”。如图:

这种方案,虽然耗时短,但中断较多,带来了上下文切换频繁的问题。

因为每种方案都其适用场景和缺点,因此在实际应用中,会根据实际情况选择方案。

例: (オブジェクト/秒) 比率が低い場合、割り込み実行 GC の頻度は低くなり、多数のオブジェクトが長期間「存続」する場合、世代別処理の利点は次のとおりです。素晴らしいものではありません。

上記は私があなたのためにまとめたものです。

関連記事:

JavaScript でクラスとオブジェクトを作成する (グラフィック チュートリアル)

JavaScript のアロー関数の詳細な説明 (グラフィック チュートリアル)

JavaScript での詳細な回答 (グラフィック チュートリアル)

以上がJavaScriptのガベージコレクションの仕組みを徹底理解_基礎知識の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。