首頁 > web前端 > js教程 > javascript垃圾收集機制與記憶體洩漏詳細解析_javascript技巧

javascript垃圾收集機制與記憶體洩漏詳細解析_javascript技巧

WBOY
發布: 2016-05-16 17:16:27
原創
948 人瀏覽過

javascript具有自動垃圾收集機制,也就是說,執行環境會負責管理程式碼執行過程中的使用的記憶體。而在C和C 之類的語言中,開發人員的一項基本任務就是手動追蹤記憶體的使用情況,這是造成許多問題的一個根源。在編寫javascript程式時候,開發人員不用再關心記憶體使用的問題,所需記憶體的分配 以及無用的回收完全實現了自動管理。這種垃圾收集機制的原理其實很簡單:找出那些不再繼續使用的變量,然後釋放其中佔用的記憶體。為此,垃圾收集器會依照固定的時間間隔(或程式碼執行中預設的收集時間),週期性的執行此操作。

下面我們來分析一下函數中局部變數正常的生命週期。局部變數只在函數執行的過程中存在。而在這個過程中,會為局部變數在堆疊(或堆)記憶體上分配對應的空間,以便儲存他們的值。然後在函數中是使用這些變量,直到函數執行結束。此時,局部變數就沒有存在的必要了,因此可以釋放他們的記憶體以供將來使用。在這種情況下,很容易判斷變數是否還有存在的必要;但並非所有情況下都這麼容易就能得出結論。垃圾收集器必須追蹤哪個變數有用哪個變數沒用,對於不再有用的變數打上標記,以備將來回收其佔用的記憶體。用於標識無用變數的策略可能會因現實而異,但具體到瀏覽器中的實現,通常有兩個策略。

標記清除

javascript中最常用的垃圾收集方式是標記清除(mark-and-sweep)。當變數進入環境(例如,在函數中宣告一個變數)時,就將這個變數標記為「進入環境」。從邏輯上講,永遠不能釋放進入環境的變數所佔的內存,因為只要執行流進入對應的環境,就可能使用它們。而當變數離開環境時,這將其標記為「離開環境」。

可以使用任何方式來標記變數。例如,可以透過翻轉某個特殊的位元來記錄變數何時進入環境,或是使用一個「進入環境的」變數列表及一個「離開環境的」變數列表來追蹤哪個變數發生了變化。說到底,如何標記變數其實並不重要,關鍵採取什麼策略。

垃圾收集器在運行的時候會為儲存在記憶體中的所有變數加上標記(當然,可以使用任何標記方式)。然後,它會去掉環境中變數以及被環境中的變數所引用的變數標記。而在此之後再被加上標記的變數將被視為準備刪除的變量,原因是環境中的變數已經無法存取這些變數了。最後,垃圾收集器完成記憶體清除工作,銷毀那些標記的值並回收它們所佔用的記憶體空間。

到2008年為止,IE、Firefox、Opera、Chrome和Safari的javascript實現使用的都是標記清除式的垃圾收集策略(或類似的策略),只不過垃圾收集的時間間隔互有不同。

引用計數

另一個不太常見的垃圾收集策略叫做引用計數(reference counting)。引用計數的意思是追蹤記錄每個值被引用的次數。當宣告一個變數並將引用型別的值賦給該變數時,則這個值的引用次數就是1。如果同一個值又被賦給另一個變量,則該值的引用次數加1。相反,如果包含這個值引用的變數又取得另外一個值,則這個值的引用次數減1.當這個值的引用次數變成0時,則表示沒有辦法存取這個值了,因此就可以將其佔用的記憶體空間回收回來。這樣當垃圾收集器下次再運作時,它就會釋放那些引用次數為零的值所佔用的記憶體。

Netscape Navigator 3.0是最早使用引用計數策略的瀏覽器,但很快它就遇到了一個嚴重的問題:循環引用。循環引用指的是物件A中包含一個指向物件B的引用,而物件B中也包含一個指向物件A的引用。

請看下面範例:

複製程式碼 代碼如下
function () {
    var objectA = new Object();
    var objectB = new Object();
objectB = new B.anotherObject = objectA;
}


在這個例子中,objectA和objectB透過各自的屬性互相引用,也就是說,這兩個物件的引用次數都是2。在採用引標記清除略的實作中,由於函數執行之後,這兩個物件都離開了作用域。因此這兩種相互引用不是個問題。但在採用引用計數策略的實作中,但函數執行完畢後,objectA和objectB還會繼續存在,因此他們的引用次數永遠不會是0。假如這個函數被重複調用,就會導致大量的記憶體無法回收。 因此,Netscape在Navigator 4.0中放棄了引用計數器方式,轉而採用標記清除來實現對其垃圾回收機制。 可是,引用計數導致的麻煩並未就此終結。

我們知道,IE中有一部分物件並不是原生javascript物件。例如,其中BOM和DOM中的物件就是使用C 以COM (Component Object Model,組件物件模型)物件的形式實現的,而COM物件的垃圾收集機制所採用的就是引用計數策略。因此,即使IE的javascript引擎是使用標記清除策略來實現的,但javascript存取的COM物件仍然是基於引用計數策略的。 換句話說,只要IE中設計COM對象,就會有循環引用的問題。

下面這個簡單的例子,展示了使用COM物件導致的循環引用問題:

複製程式碼



複製程式碼


複製程式碼


複製程式碼 程式碼如下:var element = document.getElementById("some_element");
var myObject = new Object();
myObject.element = element; .somObject = myObject;


這裡例子在一個DOM元素(element)與一個原生的javascript物件(myObject)之間建立了循環引用。其中,變數myObject 有一個名為element的屬性指向element物件;而變數element也有一個屬性名叫someObject回指myObject。由於存在這個循環引用,即使將例子中的DOM從頁面中移除,它也永遠不會被回收。
為了避免類似這樣的循環引用問題,最好是不使用他們的時候手動斷開原生javascript物件與DOM元素之間的連接。例如,可以使用下面的程式碼消除前面範例建立的循環參考:複製程式碼 程式碼如下: myObject.element = null;element.somObject = null;
変数を null に設定すると、変数とその変数が以前に参照していた値との間の接続が切断されることになります。ただし、次回ガベージ コレクターが実行されると、これらの値は削除され、それらが占有しているメモリは再利用されます。

パフォーマンスの問題

ガベージ コレクターは定期的に実行され、変数に割り当てられたメモリの量が客観的であれば、リサイクルの作業負荷も非常に大きくなります。この場合、ガベージ コレクションの間隔を決定することが非常に重要な問題になります。ガベージ コレクターが実行される頻度について考えると、IE の悪名高いパフォーマンスの問題を考えずにはいられません。 IE のガベージ コレクターは、メモリ割り当て、具体的には 256 個の変数、4096 個のオブジェクト (または配列) リテラルと配列要素 (スロット)、または 64KB 文字列に基づいて実行されます。上記のしきい値のいずれかに達すると、ガベージ コレクターが実行されます。この実装の問題は、スクリプトにこれだけ多くの変数が含まれている場合、スクリプトはその存続期間中、その多くの変数を維持する可能性が高いことです。その結果、ガベージ コレクターを頻繁に実行する必要がある場合があります。その結果、IE7 のガベージ コレクション ルーチンの最初の書き換えにより、重大なパフォーマンスの問題が発生しました。

IE7 のリリースにより、JavaScript エンジンのガベージ コレクション ルーチンの動作方法が変更されました。ガベージ コレクションをトリガーする変数割り当て、リテラル、および/または配列要素の重要な値は、動的修正に合わせて調整されます。 IE7 のしきい値は、初期化時に IE6 のしきい値と同じになります。ルーチンが割り当てられたメモリの 15% 未満を再利用する場合、変数、リテラル、配列要素のしきい値は 2 倍になります。ルーチンが割り当てられたメモリの 85% を再利用すると、さまざまなしきい値がデフォルト値にリセットされます。この一見単純な調整により、大量の JavaScript を含むページを実行するときの IE のパフォーマンスが大幅に向上します。

実際、ガベージ コレクション プロセスは一部のブラウザでトリガーできますが、これを実行することはお勧めしません。 IE では、window.CollectGarbage() メソッドを呼び出すと、すぐにガベージ コレクションがポイントされます。Opera7 以降では、window.opera.collect() を呼び出すと、ガベージ コレクション ルーチンも開始されます。

メモリを管理する

ガベージ コレクション メカニズムを備えた言語でプログラムを作成すると、開発者は通常、メモリ管理の問題を心配する必要がなくなります。ただし、メモリ管理とガベージ コレクションにおいて JavaScript が直面する問題は、まだ少し異なります。最も重要な問題の 1 つは、通常、Web ブラウザに割り当てられる使用可能なメモリの量が、デスクトップ アプリケーションに割り当てられるメモリの量よりも少ないことです。これの目的は主にセキュリティ上の理由で、JavaScript を実行している Web ページがすべてのシステム メモリを使い果たし、システムがクラッシュするのを防ぐことです。メモリ制限の問題は、変数へのメモリの割り当てに影響を与えるだけでなく、コール スタックやスレッドで同時に実行できるステートメントの数にも影響します。

したがって、ページが占有するメモリ量を最小限に抑えることで、ページのパフォーマンスを向上させることができます。その値を null に設定して参照を解放することが最善です。この方法は逆参照と呼ばれます。このアプローチは、ほとんどのグローバル変数とグローバル オブジェクトのプロパティに使用されます。次の例に示すように、ローカル変数は環境の実行時に自動的に逆参照されます。

コードをコピー コードは次のとおりです。

function createperson (name) {
var localperson = new Object();
localperson.name = name;
return localperson;
};
var gllbalperson = createperson("ニコラス");

// globalperson を手動で逆参照します
globalperson = null;


この例では、変数 globalperson は、createperson() 関数によって返された値を取得します。 createPerson() 関数内で、オブジェクトを作成し、それをローカル変数 localperson に代入し、name という名前のプロパティをオブジェクトに追加します。最後に、この関数が呼び出されると、localperson が関数として返され、グローバル変数 globalperson に割り当てられます。 localPerson は createPerson() 関数の実行後に実行環境を離れるため、明示的に逆参照する必要はありません。ただし、グローバル変数 globalperson については、使用しないときに手動で逆参照する必要があります。これが、上記の例のコードの最後の行の目的です。

ただし、値の逆参照は、その値が占有しているメモリを自動的にリサイクルすることを意味するものではありません。逆参照が実際に行うことは、ガベージ コレクターが次回実行するときにその値を再利用できるように、実行環境から値を取り出すことです。

メモリリーク

IE は JScript オブジェクトと COM オブジェクトに対して異なるガベージ コレクション ルーチンを使用するため、クロージャは IE でいくつかの特別な問題を引き起こします。具体的には、HTML 要素がクロージャのスコープ チェーンに格納されている場合、その要素は破棄できないことを意味します。次の例を見てみましょう:

コードをコピーします コードは次のとおりです:

function assignHandler () {
var element = document.getElementById("someElement");
element.onclick = function () {
alert(element.id);
};
};

上記のコードは要素時間ハンドラーとしてクロージャを作成し、このクロージャは循環参照を作成します。無名関数は assignHandler() のアクティブオブジェクトへの参照を保存するため、要素への参照数を減らすことができません。無名関数が存在する限り、要素の参照番号は少なくとも 1 であるため、それが占有するメモリは決してリサイクルされません。ただし、この問題は、次のようにコードを少し書き直すことで解決できます:
コードをコピーします コードは次のとおりです:

function assignHandler () {
var element = document.getElementById("someElement");
var id = element.id;

element.onclick = function () {
alert(id);
};

element = null; 変数内でその変数を参照すると、循環参照が排除されます。ただし、この手順を実行しただけではメモリ リークの問題を解決することはできません。クロージャは、要素を含む関数アクティビティを含むアクティビティ オブジェクト全体を参照することに注意してください。クロージャが要素を直接参照していない場合でも、参照は関数を含むアクティブなオブジェクトに保存されます。したがって、要素変数を null に設定する必要があります。このようにして、DOM オブジェクトへの参照を解放し、参照の数を削減し、DOM オブジェクトが占有しているメモリを適切に再利用することができます。


説明

1. そのウィンドウ内のオブジェクトの参照を別のウィンドウに保持すると、ウィンドウを閉じてもメモリは解放されません。

2. さらに悪いことに、DOM オブジェクトへの参照を保持したまま、そのオブジェクトが配置されているウィンドウを閉じると、IE がクラッシュしてメモリ エラーが報告されます (または再起動が必要になります)。

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板