特定の割り当て順序なし |
|
動的メモリ割り当てがどのように機能するかを完全に理解するには、ポインターについてもっと時間を費やす必要がありますが、この記事の主題から大きく逸脱する可能性があります。ここでは、ポインターに関する関連知識について詳しくは紹介しません。
JavaScript でメモリを割り当てる
最初のステップとして、JavaScript でメモリを割り当てる方法について説明します。
JavaScript により、開発者はメモリ割り当てを手動で処理する責任から解放されます。JavaScript はメモリを割り当て、値を自ら宣言します。
特定の関数呼び出しでは、オブジェクトのメモリ割り当ても行われます。
メソッドは新しい値またはオブジェクトを割り当てることができます。 :
JavaScript でのメモリの使用
JavaScript で割り当てられたメモリを使用するということは、メモリの読み取りと書き込みを意味します。これは、変数または値の読み取りまたは書き込みによって実行できます。オブジェクトのプロパティの値を取得するか、関数にパラメータを渡すことによって。
メモリが不要になったら解放する
ほとんどのメモリ管理の問題はこの段階で発生します
ここで最も難しいのは、メモリの割り当てがいつ不要になるかを判断することです。 、通常、開発者はプログラム内のメモリが不要になった場所を特定し、メモリを解放する必要があります。
高級言語には、ガベージ コレクターと呼ばれるメカニズムが組み込まれています。ガベージ コレクターの役割は、割り当てられたメモリの一部が不要になった時点を検出するために、メモリの割り当てと使用状況を追跡することです。この場合、このメモリは自動的に解放されます。
残念ながら、特定のメモリが本当に必要かどうかを知るのは難しいため (アルゴリズム的に解決することはできません)、このプロセスは大まかな推定を行うだけです。
ほとんどのガベージ コレクターは、アクセスされなくなったメモリ、つまり、そのメモリを指すすべての変数がスコープ外になったメモリを収集することによって機能します。ただし、これは収集できる一連のメモリ空間を過小評価したものです。メモリの場所のどの時点でも、スコープ内にそれを指す変数が存在する可能性がありますが、その変数には再度アクセスすることはできないからです。
ガベージ コレクション
一部のメモリが本当に役立つかどうかを判断することは不可能であるため、ガベージ コレクターはこの問題を解決する方法を考え出しました。このセクションでは、主なガベージ コレクション アルゴリズムとその制限について説明し、理解します。
メモリ参照
ガベージ コレクション アルゴリズムは主に参照に依存します。
メモリ管理のコンテキストでは、オブジェクトが別のオブジェクトへのアクセス権 (暗黙的または明示的) を持っている場合、そのオブジェクトは別のオブジェクトを参照するといいます。たとえば、JavaScript オブジェクトには、そのプロトタイプへの参照 (暗黙的参照) とプロパティ値 (明示的参照) があります。
この文脈では、「オブジェクト」の概念は通常の JavaScript オブジェクトよりも広い範囲に拡張され、関数スコープ (またはグローバル字句スコープ) も含まれます。
字句スコープは、入れ子関数内で変数名がどのように解決されるかを定義します。内部関数には、親関数が返した場合でも親関数の効果が含まれます。
参照カウント ガベージ コレクション アルゴリズム
これは最も単純なガベージ コレクション アルゴリズムです。オブジェクトへの参照がない場合、次のコードに示すように、オブジェクトは「ガベージ コレクタブル」とみなされます。
ループにより問題が発生する可能性があります
When ループする場合には制限があります。以下の例では、相互参照する 2 つのオブジェクトが作成され、ループが作成されます。関数呼び出しはスコープ外になるため、事実上役に立たず、解放できます。ただし、参照カウント アルゴリズムでは、各オブジェクトが少なくとも 1 回参照されるため、それらのオブジェクトはいずれもガベージ コレクションできないと想定されます。
マーク アンド スイープ アルゴリズム
このアルゴリズムは、オブジェクトにアクセスできるかどうかを判断し、それによってオブジェクトが有用かどうかを知ることができます。アルゴリズムは次のもので構成されます。次の手順:
- ガベージ コレクターは、参照されたグローバル変数を保存するために使用される「ルート」リストを構築します。 JavaScript では、「ウィンドウ」オブジェクトはルート ノードとして使用できるグローバル変数です。
- その後、アルゴリズムはすべてのルートとその子をチェックし、それらをアクティブとしてマークします (つまり、それらはガベージではありません)。根が届かない場所はゴミとしてマークされます。
- 最後に、ガベージ コレクターは、アクティブとしてマークされていないすべてのメモリ ブロックを解放し、そのメモリをオペレーティング システムに返します。
#「オブジェクトが参照されていない」とはオブジェクトにアクセスできないことを意味するため、このアルゴリズムは前のアルゴリズムよりも優れています。
2012 年現在、すべての最新ブラウザにはマークスイープ ガベージ コレクターが搭載されています。過去数年間に JavaScript ガベージ コレクション (世代別/増分/同時/並列ガベージ コレクション) の分野で行われたすべての改善は、アルゴリズム (マーク スイープ) の実装の改善であり、ガベージ コレクション アルゴリズム自体の改善ではありません。オブジェクトがアクセス可能かどうかを判断することが目的ですか。
この記事では、マークスイープ アルゴリズムとその最適化など、ガベージ コレクションの追跡について詳しく説明します。
ループはもはや問題ではありません
上記の最初の例では、関数呼び出しが返された後、2 つのオブジェクトはグローバル オブジェクトからアクセスできるオブジェクトによって参照されなくなります。したがって、ガベージ コレクターはそれらにアクセスできないと判断します。
オブジェクト間に参照はありますが、ルート ノードからは参照できません。
ガベージ コレクターの直観に反する動作
ガベージ コレクターは非常に便利ですが、独自のトレードオフがあり、その 1 つは非決定性です。予想どおりではありませんが、ガベージ コレクションがいつ発生するかは実際にはわかりません。これは、場合によっては、プログラムが実際に必要以上のメモリを使用することを意味します。特に速度に敏感なアプリケーションでは、短い一時停止が目立つ場合があります。メモリが割り当てられていない場合、GC の大部分はアイドル状態になります。次のシナリオを見てください。
- かなり大きなインナーセットを割り当てます。
- これらの要素のほとんど (またはすべて) はアクセス不能としてマークされます (参照が不要になったキャッシュを指していると仮定します)。
- これ以上の割り当てはありません
これらのシナリオでは、ほとんどの GC はコレクションを続行しません。つまり、コレクションに使用できるアクセスできない参照が存在する場合でも、コレクターはそれらを宣言しません。これらは厳密にはリークではありませんが、それでも通常よりも多くのメモリ使用量が発生する可能性があります。
メモリ リークとは何ですか?
基本的に、メモリ リークは次のように定義できます。アプリケーションで不要になり、何らかの理由でオペレーティング システムに戻らないメモリまたは空きメモリプール。
プログラミング言語はさまざまなメモリ管理方法をサポートしています。ただし、特定のメモリを使用するかどうかは実際には未定です。言い換えれば、メモリの一部をオペレーティング システムに返すことができるかどうかを判断できるのは開発者だけです。
プログラミング言語の中には、開発者に支援を提供するものもありますが、開発者がメモリがいつ使用されなくなるかを明確に理解することを期待するものもあります。ウィキペディアには、手動および自動のメモリ管理に関する優れた記事がいくつかあります。
4 つの一般的なメモリ リーク
1. グローバル変数
JavaScript は、未宣言の変数を興味深い方法で処理します。未宣言の変数の場合、新しい変数は次のようになります。それを参照するためにグローバル スコープ内に作成されます。ブラウザでは、グローバル オブジェクトはウィンドウです。例:
function foo(arg) {
bar = "some text";
}
ログイン後にコピー
は次と同等です:
function foo(arg) {
window.bar = "some text";
}
ログイン後にコピー
bar が foo 関数のスコープ内の変数を参照しているにもかかわらず、var を使用して宣言するのを忘れた場合、予期しないグローバルが作成されます。変数。この例では、単純な文字列が欠けていても大きな害はありませんが、間違いなく悪影響を及ぼします。
予期しないグローバル変数を作成するもう 1 つの方法は、これを使用することです:
function foo() {
this.var1 = "potential accidental global";
}
// Foo自己调用,它指向全局对象(window),而不是未定义。
foo();
ログイン後にコピー
これを回避するには、JavaScript ファイルの先頭に「use strict」を追加します。これにより、A stricter がオンになります。グローバル変数の誤った作成を防ぐための JavaScript 解析モード。
未知のグローバル変数について話していますが、依然として明示的なグローバル変数で満たされたコードが大量にあります。定義上、これらは収集可能ではありません (空として指定されているか再割り当てされていない限り)。大量の情報を一時的に保存および処理するために使用されるグローバル変数は特に懸念されます。大量のデータを保存するためにグローバル変数を使用する必要がある場合は、完了時に必ず null を指定するか、再割り当てしてください。
2. 忘れられたタイマーとコールバック
setInterval
は JavaScript でよく使用されるため、例として取り上げます。
var serverData = loadData();
setInterval(function() {
var renderer = document.getElementById('renderer');
if(renderer) {
renderer.innerHTML = JSON.stringify(serverData);
}
}, 5000); //每五秒会执行一次
ログイン後にコピー
上記のコード スニペットは、タイマーを使用して不要になったノードまたはデータを参照する方法を示しています。
レンダラーによって表されるオブジェクトは将来のある時点で削除される可能性があり、その結果、内部ハンドラー内のコード ブロック全体が不要になります。ただし、タイマーがまだアクティブであるため、ハンドラーを収集できず、その依存関係も収集できません。これは、大量のデータを格納するserverDataを収集できないことを意味します。
オブザーバーを使用する場合は、使用が終了した後 (オブザーバーが不要になるか、オブジェクトがアクセスできなくなるため)、オブザーバーを削除する明示的な呼び出しを行う必要があります。
開発者は、使用し終わったら、必ず明示的に削除する必要があります (そうしないと、オブジェクトにアクセスできなくなります)。
以前は、一部のブラウザーはこのような状況に対処できませんでした (IE6 など)。幸いなことに、最近のほとんどのブラウザはこれを自動的に実行します。リスナーを削除するのを忘れた場合でも、監視対象のオブジェクトにアクセスできなくなると、自動的にオブザーバー ハンドラーが収集されます。ただし、オブジェクトが破棄される前に、これらのオブザーバーを明示的に削除する必要があります。例:
現在のブラウザ (IE および Edge を含む) は、これらの循環参照を即座に検出して処理できる最新のガベージ コレクション アルゴリズムを使用しています。つまり、ノードを削除する前に、removeEventListener を呼び出す必要はありません。
JQuery などの一部のフレームワークまたはライブラリは、ノードを破棄する前にリスナーを自動的に削除します (特定の API を使用する場合)。これは、IE 6 などの問題のあるブラウザで実行している場合でもメモリ リークが発生しないことを保証するライブラリ内のメカニズムによって実装されています。
3. クロージャ
クロージャは JavaScript 開発の重要な側面であり、内部関数は外部 (囲まれた) 関数の変数を使用します。 JavaScript の実行方法の詳細により、次のような方法でメモリ リークが発生する可能性があります:
このコードは 1 つのことを実行します: 毎回 replaceThing
が呼び出されると、 theThing
は、大きな配列と新しいクロージャ (someMethod) を含む新しいオブジェクトを取得します。同時に、変数 unuse
d は `originalThing
を参照するクロージャを指します。
混乱していますか? 重要なことは、同じ親スコープの複数のクロージャを持つスコープを作成すると、このスコープを共有できるということです。
この場合、クロージャ someMethod
に対して作成されたスコープは unused
共有できます。 未使用
originalThing
への内部参照があります。 unused
が一度も使用されない場合でも、someMethod
は theThing
によって replaceThing
のスコープ外 (たとえば、グローバル スコープ内) に渡すことができます。呼ばれる。
someMethod
は unused
クロージャのスコープを共有しているため、含まれている originalThing
への unused
参照により強制的にそれは生き続けます (2 つのクロージャ間の共有スコープ全体)。これにより、収集ができなくなります。
このコードを繰り返し実行すると、メモリ使用量が着実に増加していることがわかりますが、GC
を実行してもメモリ使用量は減りません。基本的に、クロージャのリンクされたリストが実行時に作成され (そのルートは変数 theThing
の形式で存在します)、各クロージャのスコープは大きな配列を間接的に参照します。これにより、かなりのメモリ リークが発生しました。
4. DOM からの参照の切り離し
DOM ノードをデータ構造に保存すると便利な場合があります。テーブル内の複数の行をすばやく更新したい場合は、各 DOM 行への参照をディクショナリまたは配列に保存できます。このように、同じ DOM 要素に対して 2 つの参照が存在します。1 つは DOM ツリー内に、もう 1 つはディクショナリ内にあります。将来のある時点でこれらの行を削除する場合は、両方の参照にアクセスできないようにする必要があります。
DOM ツリー内の内部ノードまたはリーフ ノードを参照するときに考慮すべき別の問題があります。コード内にテーブル セル (
タグ) への参照を保持し、その特定のセルへの参照を保持したまま DOM からテーブルを削除すると、メモリ リークが発生する可能性があります。 ガベージ コレクターがそのセル以外のすべてを解放すると考えるかもしれません。ただし、これは当てはまりません。セルはテーブルの子ノードであり、子ノードは親ノードへの参照を保持しているため、テーブルのセルへのこの参照は テーブル全体をメモリ内に保持します。ノードを削除するには、その子ノードを削除します。
|