1. ガベージ コレクション メカニズム - GC
JavaScript には自動ガベージ コレクション メカニズム (GC: Garbage Collection) があり、コードの実行中に使用されるメモリは実行環境が管理する必要があります。
原則: ガベージ コレクターは定期的に (定期的に) 使用されなくなった変数を見つけて、そのメモリを解放します。
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 メモリ リークの問題がないことに注意してください。Microsoft が最適化を行ったか、Dom のリサイクル方法が変更された可能性があります。
4. メモリ管理
1. ガベージ コレクションはいつトリガーされますか?
大量のメモリが割り当てられている場合、ガベージ コレクションの間隔を決定することは非常に困難になります。 IE6 のガベージ コレクションは、環境内に 256 個の変数、4096 個のオブジェクト、または 64k の文字列がある場合に、メモリ割り当ての量に基づいて実行されます。これは非常に科学的であり、段落を押す必要はありません。毎回一度だけ呼び出されますが、必要ない場合もあります。オンデマンドで呼び出すのは良いことではないでしょうか?しかし、環境内に非常に多くの変数があり、スクリプトが非常に複雑である場合、これは正常なことですが、その結果、ガベージ コレクターが常に動作しているため、ブラウザーは再生できません。
Microsoft は IE7 で調整を行いました。ガベージ コレクターによって回復されるメモリ割り当て量が占有メモリの 15% 未満の場合、トリガー条件は動的に変更されます。プログラムでは、メモリの大部分が再利用できないことを意味します。このとき、再利用されるメモリの割合が 85% を超えると、ガベージ コレクションのトリガー条件が 2 倍になります。メモリはずっと前にクリーンアップされているはずです。この時点で、トリガー条件を元に戻してください。これにより、ガベージコレクション機能が非常に簡単になります
2. リーズナブルな GC プラン
1) Javascript エンジンの基本的な GC ソリューションは (単純な GC): マークとスイープ、つまり:
2)、GC の欠陥
他の言語と同様、JavaScript の GC 戦略では問題を回避できません。GC 中に他の操作への応答が停止します。これはセキュリティ上の理由によるものです。 JavascriptのGCは100ms以上なので、一般的なアプリケーションでは問題ありませんが、比較的高い継続性が要求されるJSゲームやアニメーションアプリケーションでは厄介です。これは、新しいエンジンが最適化する必要があるものであり、GC によって引き起こされる応答の長い一時停止を回避します。
3)、GC 最適化戦略
Uncle David は主に 2 つの最適化プランを紹介しました。これらは最も重要な 2 つの最適化プランでもあります。
(1) 世代 GC (世代 GC)
これは Java リサイクル戦略の考えと一致しています。その目的は、「一時的」オブジェクトと「永続的」オブジェクトを区別し、より多くの「一時的オブジェクト」領域 (若い世代) と、より少ない「永続的オブジェクト」領域 (テニュア世代) をリサイクルして、毎回走査する必要があるオブジェクトの数を減らすことです。これにより、各 GC にかかる時間が削減されます。写真に示すように:
ここで追加する必要があるのは、tenured 世代オブジェクトの場合、若い世代から tenured 世代への移行という追加のオーバーヘッドがあることです。さらに、それが参照されている場合は、参照ポイントも変更する必要があります。
(2) インクリメンタル GC
このソリューションのアイデアは非常にシンプルです。つまり、「毎回少しずつ処理し、次回も少しずつ処理する、というようにする」というものです。写真に示すように:
この解決策は短時間で完了しますが、中断が多く、頻繁にコンテキストが切り替わるという問題が発生します。
各ソリューションには適用可能なシナリオと欠点があるため、実際のアプリケーションでは、実際の状況に基づいてソリューションが選択されます。
例: (オブジェクト/秒) 比率が低い場合、割り込み実行 GC の頻度は低くなり、多数のオブジェクトが長期間「存続」する場合は単純な GC が低くなります。処理はあまり良くありません。
参考:
上記は JavaScript のガベージ コレクションのメカニズムとメモリ管理に関するものであり、皆さんの学習に役立つことを願っています。