ホームページ >ウェブフロントエンド >jsチュートリアル >JavaScriptのガベージコレクション機構とメモリリークの詳細分析_JavaScriptスキル
JavaScript には自動ガベージ コレクション メカニズムがあり、コードの実行中に使用されるメモリは実行環境が管理する必要があります。 C や C++ などの言語では、開発者にとっての基本的なタスクはメモリ使用量を手動で追跡することですが、これが多くの問題の原因となります。 JavaScript プログラムを作成する際、開発者は、必要なメモリの割り当てと無駄なリサイクルが完全に自動化されているため、メモリの使用量を気にする必要がなくなります。このガベージ コレクション メカニズムの原理は実際には非常に単純です。使用されなくなった変数を見つけて、それらが占有しているメモリを解放します。これを行うために、ガベージ コレクターは、一定の時間間隔 (またはコード実行で事前に設定された収集時間) に従ってこの操作を定期的に実行します。
関数内のローカル変数の通常のライフサイクルを分析してみましょう。ローカル変数は関数の実行中にのみ存在します。このプロセスでは、ローカル変数の値を保存するために、対応するスペースがスタック (またはヒープ) メモリ上にローカル変数に割り当てられます。これらの変数は、関数の実行が終了するまで関数内で使用されます。この時点で、ローカル変数は存在する必要がなくなるため、将来の使用のためにそのメモリを解放できます。この場合、変数がまだ必要かどうかを判断するのは簡単ですが、この結論はすべての場合においてそれほど簡単ではありません。ガベージ コレクターは、どの変数が役に立ち、どの変数が役に立たないかを追跡し、役に立たなくなった変数にマークを付けて、変数が占有しているメモリを将来再利用できるようにする必要があります。役に立たない変数を特定するために使用される戦略は実装によって異なる場合がありますが、ブラウザーの実装に応じて、通常は 2 つの戦略があります。
マークをクリア
JavaScript で最も一般的に使用されるガベージ コレクション方法は、マーク アンド スイープです。変数が環境に入ると (たとえば、関数で変数を宣言した場合)、その変数には「環境に入った」というマークが付けられます。論理的には、環境に入る変数によって占有されているメモリは、実行フローが対応する環境に入るたびに使用される可能性があるため、解放することはできません。そして、変数が環境から離れると、これはそれを「環境から離れる」こととしてマークします。
変数は任意の方法でマークできます。たとえば、特別なビットを反転して変数が環境に入ったときを記録したり、「環境に入る」変数のリストと「環境から出る」変数のリストを使用してどの変数が変化したかを追跡したりできます。結局のところ、変数をどのようにマークするかはあまり重要ではなく、どのような戦略を採用するかが重要なのです。
ガベージ コレクターが実行されると、メモリに格納されているすべての変数にマークが付けられます (もちろん、任意のマーク付け方法を使用できます)。次に、環境内の変数と、環境内の変数によって参照される変数タグを削除します。これ以降にマークされた変数は、環境内の変数がこれらの変数にアクセスできなくなるため、削除される変数とみなされます。最後に、ガベージ コレクターはメモリのクリーンアップ作業を完了し、マークされた値を破棄し、それらが占有しているメモリ領域を再利用します。
2008 年の時点で、IE、Firefox、Opera、Chrome、Safari の JavaScript 実装はすべて、マークアンドスイープ ガベージ コレクション戦略 (または同様の戦略) を使用していますが、ガベージ コレクションの間隔は異なります。
参照カウント
もう 1 つのあまり一般的ではないガベージ コレクション戦略は、参照カウントと呼ばれます。参照カウントの意味は、各値が参照された回数を追跡することです。変数を宣言し、その変数に参照型の値を割り当てると、この値への参照の数は 1 になります。同じ値が別の変数に代入されている場合、その値の参照カウントは 1 増加します。逆に、この値への参照を含む変数が別の値を取得した場合、この値への参照回数は 1 つ減ります。この値への参照回数が 0 になると、その値にアクセスする方法がなくなったことを意味します。占有されたメモリ領域が再利用されます。次に、ガベージ コレクターが実行されるときに、参照がゼロの値によって占められていたメモリが解放されます。
Netscape Navigator 3.0 は、参照カウント戦略を使用した最初のブラウザでしたが、すぐに循環参照という深刻な問題に遭遇しました。循環参照は、オブジェクト A にオブジェクト B への参照が含まれており、オブジェクト B にもオブジェクト A への参照が含まれていることを意味します。
次の例を参照してください:
IE の一部のオブジェクトはネイティブ JavaScript オブジェクトではないことがわかっています。たとえば、BOM および DOM 内のオブジェクトは、C を使用して COM (コンポーネント オブジェクト モデル) オブジェクトの形式で実装され、COM オブジェクトのガベージ コレクション メカニズムでは参照カウント方式が使用されます。したがって、IE の JavaScript エンジンはマーク アンド クリア戦略を使用して実装されていますが、JavaScript によってアクセスされる COM オブジェクトは依然として参照カウント戦略に基づいています。 言い換えれば、COM オブジェクトが IE で設計されている限り、循環参照の問題が発生します。
次の簡単な例は、COM オブジェクトの使用によって発生する循環参照の問題を示しています。
このような循環参照の問題を回避するには、ネイティブ JavaScript オブジェクトが使用されていないときに DOM 要素から手動で切断することが最善です。たとえば、次のコードを使用して、前の例で作成された循環参照を削除できます。
パフォーマンスの問題
ガベージ コレクターは定期的に実行され、変数に割り当てられたメモリの量が客観的であれば、リサイクルの作業負荷も非常に大きくなります。この場合、ガベージ コレクションの間隔を決定することが非常に重要な問題になります。ガベージ コレクターが実行される頻度について考えると、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 に設定して参照を解放することが最善です。この方法は逆参照と呼ばれます。このアプローチは、ほとんどのグローバル変数とグローバル オブジェクトのプロパティに使用されます。次の例に示すように、ローカル変数は環境の実行時に自動的に逆参照されます。
// globalperson を手動で逆参照します
globalperson = null;
ただし、値の逆参照は、その値が占有しているメモリを自動的にリサイクルすることを意味するものではありません。逆参照が実際に行うことは、ガベージ コレクターが次回実行するときにその値を再利用できるように、実行環境から値を取り出すことです。
メモリリーク
IE は JScript オブジェクトと COM オブジェクトに対して異なるガベージ コレクション ルーチンを使用するため、クロージャは IE でいくつかの特別な問題を引き起こします。具体的には、HTML 要素がクロージャのスコープ チェーンに格納されている場合、その要素は破棄できないことを意味します。次の例を見てみましょう:
1. If you keep the reference of an object in that window in another window, the memory will not be released even if you close the window;
2. What’s worse is that if you keep a reference to a DOM object and close the window where the object is located, IE will crash and report a memory error (or require a restart).