この記事ではJavaScriptのガベージコレクションの仕組みを説明します

この記事ではJavaScriptのガベージコレクションの仕組みを説明します

1. 概要

ソフトウェア開発業界の継続的な発展に伴い、パフォーマンスの最適化は避けられない話題となっています。では、どのような動作がパフォーマンスの最適化とみなされるのでしょうか?

基本的に、運用効率を向上させ、運用オーバーヘッドを削減できるあらゆる動作は、最適化操作と見なすことができます。

これは、ソフトウェアオープン業界、特にパフォーマンスの最適化が普遍的であると考えられるフロントエンド開発プロセスには、最適化する価値のある領域が数多く存在することを意味します。たとえば、リソースを要求するときに使用するネットワーク、データの転送方法、開発プロセスで使用するフレームワークなどをすべて最適化できます。

この章では、認知メモリ空間の使用からガベージコレクションまで、JavaScript 言語自体の最適化を検討し、効率的な JavaScript コードを記述できるようにします。

2. メモリ管理

近年のハードウェア技術の継続的な発展により、高級プログラミング言語にはGCメカニズムが組み込まれ、開発者はメモリ空間の使用に特別な注意を払うことなく、対応する機能の開発を正常に完了できるようになりました。なぜメモリ管理についてもう一度説明する必要があるのでしょうか? 非常に簡単なコードを使用して説明してみましょう。

まず通常の関数 fn を定義し、関数本体で配列を宣言し、配列に値を代入します。値を代入する際の添え字には、比較的大きな数値を意図的に選択することに注意してください。これを行う目的は、現在の関数が呼び出されたときに、メモリ内で可能な限り多くのスペースを適用できるようにすることです。

関数fn() {
    引数リスト = [];
    arrlist[100000] = 'これはlgです';
}

関数()

この関数を実行するプロセスには構文上の問題はありません。ただし、対応するパフォーマンス監視ツールでメモリを監視すると、メモリの変化が直線的に増加し続け、プロセスにドロップがないことがわかります。これはメモリリークを示しています。コードを書くときにメモリ管理の仕組みを十分に理解していないと、検出が容易でないメモリの問題のあるコードを書くことになります。

この種のコードが多すぎると、プログラムに予期しないバグが発生する可能性があるため、メモリ管理を習得することが非常に重要です。それでは、メモリ管理とは何かを見てみましょう。

言葉自体からわかるように、メモリは実際には読み取り可能および書き込み可能なユニットで構成されており、操作空間を表します。ここで経営陣が意図的に強調しているのは、人々が自発的にこのスペースを申請し、使用し、解放する必要があるということです。いくつかの API の助けを借りても、結局は人々が独立してこれを行うことができます。したがって、メモリ管理とは、開発者がメモリ空間を積極的に申請し、空間を使用し、空間を解放できることを意味します。したがって、プロセスは非常にシンプルで、申請、使用、リリースの合計 3 つのステップです。

JavaScript に戻ると、実は他の言語と同様に、JavaScript でもこのプロセスは 3 つのステップで実行されますが、ECMAScript では対応する操作 API は提供されていません。したがって、JavaScript は、開発者が対応する API を積極的に呼び出してメモリ空間を管理できる C や C++ のようなものにはなりません。

ただし、そうであっても、JavaScript スクリプトを通じてスペースのライフサイクルがどのように完了するかのデモンストレーションには影響しません。プロセスは非常に簡単です。まず、スペースを申請する必要があります。次に、スペースを使用する必要があります。最後に、スペースを解放する必要があります。

JavaScript には対応する API が直接提供されていないため、JavaScript 実行エンジンは変数定義ステートメントに遭遇した場合にのみ、対応するスペースを自動的に割り当てることができます。ここでは、まず変数 obj を定義し、それを空のオブジェクトにポイントします。実際の用途は、読み取り/書き込み操作です。yd の書き込みなど、このオブジェクトに特定のデータを書き込むだけです。最後に、解放します。繰り返しになりますが、JavaScript には対応する解放 API がないため、ここでは直接 null に設定するなどの間接的な方法を使用できます。

obj = {} とします

オブジェクト名 = 'yd'

オブジェクト = null

このとき、メモリ管理プロセスに従って JavaScript でメモリ管理を実装することと同等になります。後で、このようなパフォーマンス監視ツールでメモリの傾向を確認することができます。

3. ガベージコレクション

まず、JavaScript ではどのようなコンテンツがガベージとみなされるのでしょうか。ガベージの概念は後続の GC アルゴリズムにも存在し、実際にはこの 2 つはまったく同じです。ではここで統一的に説明していきましょう。

JavaScript でのメモリ管理は自動で行われます。オブジェクト、配列、または関数が作成されるたびに、対応するメモリ領域が自動的に割り当てられます。後続のプログラム コードが実行され、一部のオブジェクトが参照関係を通じて見つからなくなると、これらのオブジェクトはガベージと見なされます。または、これらのオブジェクトは実際にはすでに存在しているが、コード内の不適切な構文や構造エラーのために、これらのオブジェクトを見つける方法がない場合は、これらのオブジェクトもガベージと呼ばれます。

ゴミを発見すると、JavaScript 実行エンジンが動作し、ゴミが占めているオブジェクト領域をリサイクルします。このプロセスはガベージ コレクションと呼ばれます。ここではいくつかの小さな概念が使用されています。1 つ目は参照、2 つ目はルートからのアクセスです。この操作は、後続の GC でも頻繁に言及されます。

ここで、到達可能オブジェクトという別の用語について触れておきたいと思います。まず、JavaScript で到達可能オブジェクトを理解するのは非常に簡単です。これは、アクセスできるオブジェクトを意味します。アクセスに関しては、特定の参照を介して、または現在のコンテキスト内のスコープ チェーンを介して行うことができます。見つけられる限り、到達可能とみなされます。ただし、ここでは小さな標準的な制限があり、ルートから見つけられる場合にのみ到達可能と見なされます。それでは、ルートとは何かについて説明しましょう。JavaScript では、現在のグローバル変数オブジェクトがルート、つまりいわゆるグローバル実行コンテキストとみなされます。

簡単にまとめると、JavaScript のガベージ コレクションは、実際にはガベージを検出し、JavaScript 実行エンジンにその領域を解放してリサイクルさせることです。

ここでは参照と到達可能なオブジェクトが使用されています。次に、コードを使用して、参照と到達可能なオブジェクトが JavaScript にどのように反映されるかを確認します。

まず、変数を定義します。後で値を変更するには、letキーワードを使用してオブジェクトを指すobjを定義します。説明の便宜上、次のように名前を付けます。

obj = {name: 'xiaoming'}とします

このコード行を記述すると、実際にはこのスペースが現在の obj オブジェクトによって参照されていることになり、ここに参照が表示されます。グローバル実行コンテキストでは、ルートから obj を見つけることができます。つまり、obj に到達可能であり、間接的に現在の xiaoming のオブジェクト空間に到達可能であることを意味します。

次に、変数を再定義します。たとえば、ali を obj と等しくします。Xiao Ming のスペースには、もう 1 つの参照があると考えられます。ここで参照値が変更され、この概念は後続の参照カウント アルゴリズムで使用されます。

obj = {name: 'xiaoming'}とします

ali = objとする

もう 1 つ、obj を直接検索して null に再割り当てしてみましょう。これを実行した後、それについて考えることができます。 Xiao Ming のオブジェクト空間自体には 2 つの参照があります。 null 代入コードの実行により、obj から Xiaoming のスペースへの参照が切断されます。シャオミンのオブジェクトは今でも到達可能でしょうか?もちろんです。 ali は依然としてそのようなオブジェクト空間を参照しているため、依然として到達可能なオブジェクトです。

これは引用の主な説明ですが、ちなみに、到達可能な説明も見られます。

次に、現在の JavaScript で実行可能な操作を説明するために別の例を取り上げますが、これには事前に説明する必要があります。

後続の GC でマークスイープ アルゴリズムを容易にするために、この例はもう少し複雑になります。

まず、objGroup という関数を定義し、2 つのパラメーター obj1 と obj2 を設定し、obj1 が属性を介して obj2 を指すようにし、次に obj2 も属性を介して obj1 を指すようにします。次に、return キーワードを使用してオブジェクトを直接返し、o1 を介して obj1 を返し、次に o2 を設定して obj2 を検索できるようにします。完了後、この関数を外部から呼び出し、それを受け取る変数を設定すると、obj は objGroup 呼び出しの結果と等しくなります。渡される 2 つのパラメータは、2 つのオブジェクト obj1 と obj2 です。

関数objGroup(obj1, obj2) {
    obj1.next = obj2;
    obj2.prev = obj1;
}

obj = objGroup({name: 'obj1'}, {name: 'obj2'}); とします。

コンソールにログ出力します。

実行すると、オブジェクトが取得されたことがわかります。オブジェクトには obj1 と obj2 が含まれており、obj1 と obj2 は属性を通じて互いを指します。

{
    o1: {name: 'obj1'、次: {name: 'obj2'、前: [円形]}}、
    o2: {name: 'obj2'、次: {name: 'obj1'、次: [円形]}}
}

コードを分析すると、まずグローバル ルートから到達可能なオブジェクト obj を見つけることができます。これは、上記のように o1 と o2 を含む関数呼び出しを通じてメモリ空間を指します。次に、o1 と o2 の内部では、対応する属性は obj1 スペースと obj2 スペースを指すだけです。 obj1 と obj2 は next と prev を通じて相互に参照するため、コード内に表示されるすべてのオブジェクトをルートから検索できます。見つけるのがどれほど困難であっても、いずれにせよ見つけることはできるので、さらに分析を続けてください。

delete ステートメントを使用して、obj 内の o1 の参照と obj1 への obj2 の参照を直接削除する場合。この時点では、obj1 オブジェクト空間を直接見つける方法がないことを意味しているため、ここではガベージ操作と見なされます。最後に、JavaScript エンジンがそれを見つけてリサイクルします。

ここで説明するのは少し複雑です。簡単に言うと、コードを書くときにいくつかのオブジェクトの参照関係が存在します。ルートの下から検索すると、最終的に参照関係に従っていくつかのオブジェクトが見つかります。ただし、これらのオブジェクトへのパスが破棄またはリサイクルされていることが判明した場合、現時点ではそれらを再度見つける方法はなく、それらはガベージと見なされ、最終的にガベージ コレクション メカニズムによってリサイクルされる可能性があります。

4. GCアルゴリズムの紹介

GC は、ガベージ コレクション メカニズムの略語として理解できます。GC が動作しているとき、メモリ内のいくつかのガベージ オブジェクトが見つかり、そのスペースが解放されてリサイクルされるため、後続のコードではメモリ スペースのこの部分を引き続き使用できます。 GC でどのようなものがガベージと見なされるかについては、2 つの小さな基準があります。

1 つ目は、プログラムの要件の観点から考えることです。特定のデータが使用後のコンテキストで不要になった場合は、ゴミとして扱うことができます。

例えば、次のコード内の name は関数呼び出しが完了した後は不要になります。したがって、需要の観点からは、ゴミとしてリサイクルする必要があります。リサイクルされたかどうかについては、今は議論しません。

関数func() {
    名前 = 'yd';
    `${name} はコーダーです` を返します
}

関数()

2 番目の状況は、現在のプログラム実行中に変数を参照できるかどうかを検討することです。たとえば、以下のコードでは関数内に名前を配置しますが、今回は変数を宣言するためのキーワードが追加されています。このキーワードを使用すると、関数呼び出しが終了すると、外部空間で名前にアクセスできなくなります。したがって、彼が見つからない場合、彼は実際にはゴミとみなされる可能性があります。

関数func() {
    定数名 = 'yd';
    `${name} はコーダーです` を返します
}

関数()

GC について説明した後、GC アルゴリズムについて説明します。 GC が実際にはメカニズムであることはすでにわかっています。GC 内のガベージ コレクターは特定のリサイクル作業を完了することができ、その作業の本質は、スペースを解放してスペースをリサイクルするためのガベージを見つけることです。このプロセスには、スペースの検索、スペースの解放、スペースの再利用といういくつかのアクションがあります。このような一連のプロセスにはさまざまな方法があるはずです。GC アルゴリズムは、いくつかの数学的な計算式のように、ガベージ コレクターが作業プロセスで従ういくつかのルールとして理解できます。

一般的な GC アルゴリズムには参照カウントが含まれており、数値を使用して現在のオブジェクトがガベージかどうかを判断できます。マーク スイープは、GC がガベージかどうかを判断するときに、アクティブなオブジェクトにマークを追加できます。 Mark-sweep は、後続のリサイクル プロセスで実行できるいくつかの異なる処理を除いて、mark-sweep と非常によく似ています。世代別リサイクル、V8 で使用されるリサイクル メカニズム。

5. 参照カウントアルゴリズム

参照カウント アルゴリズムの核となる考え方は、参照カウンタを介して現在のオブジェクトへの参照数を内部的に維持し、オブジェクトの参照値が 0 かどうかを判定して、それがガベージ オブジェクトであるかどうかを判断することです。この値が 0 の場合、GC が動作を開始し、オブジェクトが配置されているスペースをリサイクルして解放します。

参照カウンタの存在により、参照カウントと他の GC アルゴリズムの実行効率に違いが生じる可能性があります。

参照値の変更とは、オブジェクトの参照関係が変更されたときに、参照カウンターが現在のオブジェクトに対応する参照値をアクティブに変更することを意味します。例えば、コード内にオブジェクト空間があり、変数名がそれを指している場合、この時点での値は +1 です。それを指している別のオブジェクトがある場合は、再び +1 になります。減少した場合は -1 になります。参照番号が 0 の場合、GC は直ちに動作して現在のオブジェクト スペースをリサイクルします。

参照関係が変化する状況を簡単なコードで説明してみましょう。まず、いくつかの単純なユーザー変数を定義し、それらを通常のオブジェクトとして扱い、次に配列変数を定義して、配列内のいくつかのオブジェクトの年齢属性値を保存します。別の関数を定義し、関数本体にいくつかの変数値 num1 と num2 を定義します。ここでは const がないことに注意してください。外部レベルで関数を呼び出します。

ユーザー1 = {年齢: 11};
ユーザー2 = {年齢: 22};
ユーザー3 = {年齢: 33};

const nameList = [user1.age, user2.age, user3.age,];

関数fn() {
    数値1 = 1;
    数値2 = 2;
}

関数fn();

まず、グローバルな視点で見ると、user1、user2、user3、nameList がウィンドウの直下に存在することがわかります。同時に、fn 関数で定義された num1 と num2 も、キーワードが設定されていないため、ウィンドウ オブジェクトの下にマウントされます。この時点で、これらの変数の参照カウントは間違いなく 0 ではありません。

次に、関数内でキーワードを使用して num1 と num2 を直接宣言します。これは、現在の num1 と num2 がスコープ内でのみ有効になることを意味します。したがって、関数呼び出しが完了すると、num1 と num2 は外部グローバル ロケーションから見つけられなくなり、num1 と num2 の参照カウントは 0 に戻ります。この時点で、0 である限り、GC はすぐに動作を開始し、num1 と num2 をガベージとしてリサイクルします。つまり、関数が実行された後、内部メモリ空間はリサイクルされます。

ユーザー1 = {年齢: 11};
ユーザー2 = {年齢: 22};
ユーザー3 = {年齢: 33};

const nameList = [user1.age, user2.age, user3.age,];

関数fn() {
    定数num1 = 1;
    定数num2 = 2;
}

関数fn();

次に、user1、user2、user3、nameList など他のものを見てみましょう。 userList は上記の 3 つのオブジェクト スペースを指しているだけなので、スクリプトが 1 回実行された後でも、user1、user2、user3 のスペースは引き続き参照されます。したがって、この時点での参照カウンタは 0 ではなく、ガベージとしてリサイクルされることはありません。これは、参照カウント アルゴリズムの実装に従う基本原則です。簡単にまとめると、現在のオブジェクトの参照カウントの値に依存して、それが 0 であるかどうかを判断し、それによってそれがガベージ オブジェクトであるかどうかを判断します。

1. 参照カウントの利点と欠点

参照カウント アルゴリズムの利点は 2 つの部分にまとめることができます。

1 つ目は、参照カウント ルールでは、現在の参照カウントが 0 かどうかに基づいてオブジェクトがガベージであるかどうかを判断できるため、ガベージが見つかるとすぐにリサイクルされるということです。そうであれば、すぐに解放できます。

2 つ目は、参照カウント アルゴリズムによってプログラムの一時停止を最小限に抑えることができることです。アプリケーションの実行中は、必然的にメモリが消費されます。現在の実行プラットフォームのメモリには上限があるため、必ずメモリがいっぱいになります。参照カウント アルゴリズムは、メモリ参照値が 0 であるオブジェクトを常に監視するため、極端な場合、メモリがいっぱいになりそうであることがわかった場合、参照カウントはすぐに値 0 のオブジェクト空間を見つけて解放します。これにより、現在のメモリがいっぱいになることがなくなり、いわゆるプログラム一時停止の削減が実現します。

参照カウントの欠点についても 2 つの説明があります。

1 つ目は、参照カウント アルゴリズムには、循環参照を持つオブジェクトのスペースを再利用する方法がないことです。次のコード スニペットは、循環参照オブジェクトが何であるかを示しています。

通常の関数 fn を定義し、関数本体内に 2 つの変数 (オブジェクト obj1 と obj2) を定義します。obj1 に obj2 を指す名前属性を持たせ、obj2 に obj1 を指す属性を持たせます。関数の最後にある return は通常の文字を返します。もちろん、これは実用的な意味はなく、単なるテストです。次に、最も外側のレベルで関数を呼び出します。

関数fn() {
    obj1 を定数で指定します。
    obj2 は定数です。

    obj1 の名前 = obj2;
    obj2 の名前 = obj1;

    'yd はコーダーです' を返します。
}

その後の分析は同じです。関数が実行された後、その内部のスペースはスペースリサイクルに関与する必要があります。たとえば、obj1 と obj2 はグローバルに参照されなくなったため、この時点では参照カウントは 0 になります。

しかし、この時点で問題が発生します。GC が obj1 を削除しようとすると、obj2 に obj1 を指す属性があることが分かります。つまり、前の規則によれば、obj1 と obj2 はグローバル スコープ内では見つかりませんが、スコープ内ではそれらの間に相互誘導関係があることは明らかです。この場合、それらの参照カウンタ値は 0 ではなく、GC にはこれら 2 つのスペースを再利用する方法がありません。これによって、オブジェクト間のいわゆる循環参照が発生し、メモリ空間が無駄になります。これは、参照カウント アルゴリズムが直面する問題でもあります。

2 番目の問題は、現在の参照カウントの値の変更を維持する必要があるため、参照カウント アルゴリズムに多くの時間がかかることです。この場合、現在のオブジェクトの参照値を変更する必要があるかどうかを常に監視する必要があります。オブジェクトの値を変更するには時間がかかります。メモリ内に変更する必要があるオブジェクトがさらにある場合、時間は非常に長くなります。したがって、他の GC アルゴリズムと比較すると、参照カウント アルゴリズムの時間オーバーヘッドは大きくなります。

6. マークアンドスイープアルゴリズム

参照カウントと比較すると、マークアンドスイープアルゴリズムの原理はより単純であり、いくつかの対応する問題も解決できます。 V8 では広く使用されています。

マーク アンド スイープ アルゴリズムの基本的な考え方は、ガベージ コレクション操作全体を 2 つのステージに分割することです。最初のステージでは、すべてのオブジェクトを走査し、マークするアクティブなオブジェクトを探します。このアクティビティは、前述の到達可能なオブジェクトと同じです。第 2 段階では、すべてのオブジェクトが引き続き走査され、マークされていないオブジェクトはクリアされます。次回 GC が正常に動作するように、第 1 段階で設定されたマークも第 2 段階で消去されることに注意してください。このようにして、現在のガベージ スペースは 2 つのトラバーサル動作を通じて再利用され、最終的にメンテナンスのために対応するフリー リストに引き渡され、後続のプログラム コードを使用できるようになります。

これがマーク アンド スイープ アルゴリズムの基本原理です。実際には、2 つの操作で構成されます。1 つ目はマーキング、2 つ目はクリアです。ここに例があります。

まず、3 つの到達可能なオブジェクト A、B、C をグローバルに宣言します。これら 3 つの到達可能なオブジェクトを見つけると、それらの下にいくつかのサブ参照があることがわかります。ここで、マークスイープ アルゴリズムが威力を発揮します。その下に子があり、さらにその子の下にも子があることが判明した場合、再帰的に到達可能なオブジェクトの検索を続行します。たとえば、D と E はそれぞれ A と C の子参照であり、到達可能としてマークされます。

ここには 2 つの変数 a1 と b1 があります。これらは関数のローカル スコープ内にあります。ローカル スコープが実行されると、スペースはリサイクルされます。したがって、a1 と b1 はグローバル チェーンの下に見つかりません。この時点で、GC メカニズムはそれをガベージ オブジェクトであると認識し、マークしません。最終的には、GC が機能するとリサイクルされます。

定数A = {};

関数fn1() {
    定数D = 1;
    AD = D;
}

関数fn1();

定数B;

定数C = {};

関数fn2() {
    定数E = 2;
    AE = E;
}

関数fn2();

関数fn3() {
    定数a1 = 3;
    定数b1 = 4;
}

関数fn3();

これは、マーク アンド スイープ プロセスでマーキング フェーズとスイープ フェーズと呼ばれ、各フェーズで実行される内容です。簡単な片付けは2つのステップに分けられます。最初の段階では、到達可能なすべてのオブジェクトが検索されます。参照の階層が関係する場合、検索は再帰的に実行されます。検索が完了すると、これらの到達可能なオブジェクトがマークされます。マーキングが完了すると、第 2 段階でクリーンアップが開始され、マークされていないオブジェクトが検索され、第 1 段階で作成されたマークがクリアされます。これにより、ガベージ コレクションが完了します。同時に、回復された領域は最終的にフリー リストに直接配置されることに注意してください。以降の手続きをスムーズに行うために、こちらから直接スペースを申請することができます。

1. マークスイープアルゴリズムの利点と欠点

参照カウントと比較して、マークスイープには、オブジェクトの循環参照のリサイクル操作を解決できるという大きな利点が 1 つあります。コードを書くときに、A、B、C などのグローバルに到達可能なオブジェクトを定義する場合があります。また、関数内で a1 と b1 を定義し、それらが相互に参照できるようにするなど、関数のローカル スコープがいくつかある場合もあります。

定数A = {};

定数B;

定数C = {};

関数fn() {
    定数a1 = {};
    b1 は定数です。
    a1.値 = b1;
    b1 の値 = a1;
}

関数fn();

関数呼び出しが終了したら、その内部のスペースを解放する必要があります。この場合、関数呼び出しが終了すると、そのローカル スペース内の変数はグローバル スコープへのリンクを失います。現時点では、a1 と b1 はグローバル ルート下ではアクセスできず、到達不能なオブジェクトです。到達不可能なオブジェクトはマーキング フェーズではマークできず、リサイクルの 2 番目のフェーズで直接解放されます。

これは mark-sweep で実行できることですが、参照カウントでは関数呼び出しが終了し、グローバルにアクセスする方法はありません。しかし、現在の判断基準は参照数が 0 かどうかであるため、この場合、a1 と b1 のスペースを解放する方法はありません。これは、もちろん、参照カウント アルゴリズムと比較したマーク アンド スイープ アルゴリズムの最大の利点です。

同時に、マークアンドスイープアルゴリズムにはいくつかの欠点もあります。例えば、メモリの保存状況をシミュレートして、ルートから検索すると、下部に到達可能なオブジェクト A があり、ルートから直接検索できない左側と右側の領域 B と C があります。この場合、2 回目のクリア操作中に、B と C に対応するスペースが直接再利用されます。解放されたスペースはフリー リストに追加され、後続のプログラムはフリー リストから対応するスペース アドレスを直接申請して使用できるようになります。この状況には問題があります。

関数fn() {
    定数B = '2';
}
関数fn();

const A = '4文字';

関数fn2() {
    定数C = 'a';
}
関数fn2();

たとえば、どのスペースも 2 つの部分で構成され、1 つはスペースのサイズやアドレスなどのメタデータを格納するために使用され、ヘッダーと呼ばれる部分であると考えられます。また、ドメインと呼ばれる、データを格納するために特別に使用される部分もあります。B スペースと C スペースは、B オブジェクトに 2 ワード分のスペースがあり、C オブジェクトに 1 ワード分のスペースがあることを前提としています。この場合、リサイクルされているとはいえ、合計で 3 語分のスペースが解放されているように見えますが、それらは A オブジェクトによって分割されています。そのため、リリースが完了した後も、それらはまだ散在しており、つまりアドレスは連続していません。

これは非常に重要です。後で申請したいスペースのアドレスサイズはちょうど 1.5 ワードです。この場合、B によって解放されたスペースを直接探すと、0.5 多いため、多すぎることがわかります。C によって解放されたスペースを直接探すと、1 しかないため、足りないことがわかります。したがって、マークアンドスイープアルゴリズムの最大の問題である空間の断片化が発生します。

いわゆる空間の断片化とは、現在リサイクル中のゴミオブジェクトのアドレスが連続していないことです。この不連続性により、リサイクル後にゴミオブジェクトはさまざまな隅に散らばります。後でそれらを使用したい場合、新しく生成されたスペースがそれらのサイズと一致していれば、直接使用できます。多すぎても少なすぎても使用には適しません。

以上がマークアンドスイープアルゴリズムのメリットとデメリットです。簡単に言うと、メリットは再利用できない循環参照の問題を解決できること、デメリットはスペースの断片化を引き起こし、スペースを最大限に活用できないことです。

7. マークスイープアルゴリズム

マーク照合アルゴリズムは V8 で頻繁に使用されます。その実装方法を見てみましょう。

まず、マークスイープ アルゴリズムはマークスイープの拡張操作であると考えられます。最初の段階ではまったく同じです。すべてのオブジェクトを走査し、到達可能なアクティブ オブジェクトをマークします。クリアの 2 番目の段階では、マーク スイープによってマークされていないガベージ オブジェクトのスペースが直接再利用されますが、マーク コンパクションではクリアの前にコンパクション操作が実行され、オブジェクトの位置がアドレス内で連続するように移動されます。

リサイクル前には多くのアクティブ オブジェクトと非アクティブ オブジェクト、およびいくらかの空き領域があると仮定すると、マーキング操作を実行すると、すべてのアクティブ オブジェクトがマークされ、その後にソート操作が実行されます。ソートは実際には位置の変更です。アクティブなオブジェクトが最初に移動され、アドレスが連続するようになります。すると、アクティブ オブジェクトの右側の範囲全体がリサイクルされ、マーク スイープ アルゴリズムに比べて明らかな利点が得られます。

メモリ内に小さな領域が多数散在することはないため、回収された領域は基本的に連続しています。これにより、その後の使用時に解放されたスペースを最大限に活用できるようになります。このプロセスはマーク コンパクト アルゴリズムであり、マーク スイープと連携して V8 エンジンで頻繁な GC 操作を実装します。

8. 実行タイミング

1 つ目は参照カウントで、これによりガベージ オブジェクトを時間内にリサイクルできます。値が 0 である限り、GC はすぐにこのスペースを見つけてリサイクルし、解放します。この機能のおかげで、参照カウントはプログラムの詰まりを最小限に抑えることができます。スペースがいっぱいになりそうになると、ガベージ コレクターがメモリを解放して、メモリ スペースに常に使用可能なスペースが確保されるからです。

マークスイープはガベージオブジェクトをすぐにリサイクルすることができず、クリアすると現在のプログラムは実際に動作を停止します。第一段階でゴミが見つかったとしても、第二段階で片付けられるまでリサイクルされません。

マーク圧縮では、ガベージ オブジェクトをすぐに再利用することもできません。

9. V8エンジン

ご存知のとおり、V8 エンジンは市場で最も主流の JavaScript 実行エンジンです。日常的に使用される Chrome ブラウザや NodeJavaScript プラットフォームは、このエンジンを使用して JavaScript コードを実行します。これら 2 つのプラットフォームで JavaScript が効率的に実行できるのは、まさに V8 の存在によるものです。 V8 が高速な理由は、優れたメモリ管理機構を備えていることに加え、ジャストインタイムコンパイルを使用しているという特徴もあります。

以前は、多くの JavaScript エンジンでは実行前にソース コードをバイトコードに変換する必要がありましたが、V8 ではソース コードを直接実行できるマシン コードに変換できます。そのため実行速度が非常に速くなります。

V8 のもう一つの大きな特徴は、メモリに上限があることです。64 ビット オペレーティング システムでは、上限は 1.5G を超えず、32 ビット オペレーティング システムでは、その値は 800M を超えません。

V8 がなぜこのアプローチを採用したのでしょうか? その理由は基本的に 2 つの側面から説明できます。

まず、V8自体はブラウザ向けに作られているので、既存のメモリサイズで十分使えます。さらに、V8 に実装されているガベージ コレクション メカニズムでも、このような設定を採用することが非常に合理的であると判断しています。公式がテストを行ったところ、ガベージメモリが 1.5G に達すると、V8 は増分マーキングアルゴリズムを使用してガベージコレクションを実行するのに 50 ミリ秒しかかからず、非増分マーキング方式を使用してガベージコレクションを実行するのに 1 秒しかかかりません。ユーザーエクスペリエンスの観点から見ると、1 秒はすでに非常に長い時間であるため、1.5G が境界として使用されます。

1. ガベージコレクション戦略

プログラムを使用する過程では大量のデータが使用され、そのデータはプリミティブデータとオブジェクト型データに分けられます。基本的な生データはプログラム言語自体によって制御されます。したがって、ここで言うリサイクルは、主にヒープ領域に残っているオブジェクトデータを指すため、このプロセスはメモリ操作と切り離すことはできません。

V8 は世代リサイクルの考え方を採用しており、一定のルールに従ってメモリ空間を新世代のストレージ領域と旧世代のストレージ領域の 2 つのカテゴリに分割します。分類後、さまざまな世代に最も効率的な GC アルゴリズムを使用して、さまざまなオブジェクトをリサイクルします。これは、V8 リサイクルでは多くの GC アルゴリズムが使用されることを意味します。

まず、世代に分割する必要があるため、世代回復アルゴリズムを使用する必要があります。次に、スペース複製アルゴリズムが使用されます。さらに、mark-sweep と mark-compact も使用されます。最後に、効率を向上させるために、マーク増分が再び使用されます。

2. 新世代のオブジェクトのリサイクル

まず最初に、V8 内のメモリ割り当てについて説明します。世代ごとのコレクションのアイデアに基づいているため、メモリスペースはV8内の2つの部分に分割されます。左側のスペースは、新しい世代のオブジェクトを保存するために使用され、右側のスペースは古い世代のオブジェクトを保存するために使用されます。新しい世代のオブジェクトスペースには、特定の設定があります。

新しい世代のオブジェクトは、実際に生存時間が短い人を指します。たとえば、現在のコードにはローカルスコープがあり、スコープの変数は、グローバルスコープなどの他の場所にも変数が完了し、グローバル変数はプログラムが終了するまでリサイクルされません。したがって、比較的言えば、新世代は、生存時間が短い変数オブジェクトを指します。

新しい世代のオブジェクトのリサイクルに使用されるアルゴリズムは、主にアルゴリズムとマークコンパクトアルゴリズムであり、左側の小さなスペースがandに呼ばれ、これらの2つの部分のサイズが等しく、空間はアイドル状態と呼ばれます。これらの2つのスペースを使用すると、コードが実行されると、スペースを適用する必要がある場合、すべての可変オブジェクトが最初にSpaceに割り当てられます。つまり、このプロセス中にアイドル状態になると、GC操作がトリガーされます。現時点では、MarkとSortを使用して、空間をマークし、アクティブなオブジェクトを見つけてから、整形操作を使用して位置を連続させて、断片化されたスペースが後で生成されないようにします。

これらの操作を完了した後、アクティブオブジェクトはTo Spaceにコピーされます。つまり、From Spaceのアクティブオブジェクトにはバックアップがあり、現時点ではリサイクルを考慮することができます。リサイクルも非常に簡単です。

要約すると、新しい世代のオブジェクトの保管エリアは、現在、これらの2つのスペースが使用されており、すべてのオブジェクト宣言がこのスペースに配置されます。 GCメカニズムがトリガーされると、すべてのアクティブオブジェクトが見つかり、ソートされ、宇宙にコピーされます。コピーが完成した後、スペース(つまり、交換名)を交換し、元のオリジナルがfromになり、元のオリジナルがbeになります。これにより、スペースのリリースと回復が完了します。

以下は、プロセスの詳細を説明しています。このプロセスで最初に思い浮かぶのは、コピー中に可変オブジェクトによって指されたスペースが見つかった場合、現在の古い世代のオブジェクトにも表示されることです。現時点では、プロモーションと呼ばれる操作が発生します。これは、新世代のオブジェクトをストレージのために古い世代に移動することです。

プロモーション操作をいつトリガーするかについては、一般的に2つの基準があります。現時点では、ストレージのために古い世代のストレージエリアにコピーできます。さらに、現在のコピープロセス中にスペースの使用率が25%を超える場合、すべてのアクティブオブジェクトをストレージのために古い世代に移動する必要があります。

なぜ25%を選ぶのですか?実際、将来リサイクル操作が実行されると、宇宙と宇宙への宇宙が最終的に交換されるため、理解するのは簡単です。つまり、以前は出身であり、以前は、使用率が80%に達すると、最終的にはアクティブなオブジェクトのストレージスペースになり、新しいオブジェクトを保存できないようになります。簡単な説明では、スペースの使用率が一定の制限を超えた場合、将来使用される場合、新しいオブジェクトのスペースが十分ではない可能性があるため、そのような制限があるということです。

簡単に要約すると、現在のメモリは2つの部分に分割され、その一部は新しい世代のオブジェクトを保存するために使用されます。次に、タグソートアルゴリズムを使用して、From Spaceをマークして整理してから、それらをTo Spaceにコピーできます。最後に、2つのスペースの状態を交換すると、スペースリリース操作が完了します。

3。老齢オブジェクトのリサイクル

古い生成オブジェクトは、V8でメモリスペースの右側に保存されます。

古い世代のオブジェクトは、たとえば長い間生き残るオブジェクトを指します。高齢のゴミコレクションの場合、3つのアルゴリズムが主に除去、マークの並べ替え、漸進的マーキングをマークするために使用されます。

それを使用すると、マーキングとクリーニングアルゴリズムは、主にゴミ空間のリリースとリサイクルを完了するために使用されます。この場所にいくつかのスペース断片化の問題があることは明らかですが、そのような問題はありますが、基礎となるV8は依然としてマーククリアリングアルゴリズムを使用しています。彼の改善速度は、空間の断片と比較して非常に明白であるためです。

マーキングソートアルゴリズムはどのような状況で使用されますか?新世代の内容を古い世代に移動する必要がある場合、およびこの時間ノードの古い世代のストレージエリアのスペースは、新世代のストレージエリアから移動したオブジェクトを保存するのに十分ではありません。この場合、マーキングが整理され、以前のロックスペースの一部が整理されてリサイクルされ、プログラムに使用するスペースが増えます。最後に、リサイクル効率を改善するために、増分マーキングが使用されます。

ここでは、新世代と古い世代のゴミリサイクルを比較します。

新世代のゴミコレクションは、コピーアルゴリズムを使用するため、スペースを使用するためにスペースを使用することに似ています。ただし、新世代の貯蔵エリア自体は非常に小さいため、分割されたスペースは小さく、スペースのこの部分によってもたらされる時間の改善はもちろん取るに足らないものです。

古い世代のオブジェクトのリサイクルプロセスでこの1桁から2桁のアプローチを採用してみませんか?古い世代の貯蔵スペースは比較的大きいため、2つに分割されている場合、数百メガバイトのスペースがあります。 2つ目は、古い世代がストレージエリアに多くのオブジェクトデータを保存するため、割り当てプロセス中に多くの時間を消費します。

前述のインクリメンタルマーキングアルゴリズムがガベージコレクション操作を最適化する方法については?まず、2つの部分に分かれています。1つはプログラムの実行、もう1つはガベージコレクションです。

第一に、ガーベージコレクションが進行中の場合、現在のJavaScriptプログラムの実行をブロックすることは明らかです。つまり、プログラムが実行された後、Garbage Collection操作を停止して実行します。いわゆるマーク増分は、単にガベージコレクション操作全体を複数の小さなステップに分割し、リサイクル全体を断片に完了し、1つの息で行われたガベージコレクション操作を置き換えることを意味します。

これの主な利点は、ゴミ収集とプログラムの実行の代替を実現することです。プログラムが以前のように実行されたときにガベージコレクションを実行できないことを避け、プログラムがGarbage Collectionによって実行されたときにプログラムを実行し続けないようにしてください。

インクリメンタルマーキングの実装原則を説明するための簡単な例を教えてください。

プログラムが最初に実行されると、ガベージコレクションをトリガーすると、どのアルゴリズムを使用しても、移動操作とマーキング操作を目的としています。トラバーサルプロセスでは、マーキングが一度に到達可能で到達可能な操作があるため、マーキングを一度に実行できることも前に言及されています。その後、停止してプログラムをしばらく実行させることができます。プログラムがしばらく実行されている場合、GCマシンは、以下のサブエレメントも到達できるように、マーク操作の2番目のステップを実行し続け、マークを続けます。ラウンドのマーキングの後、GCを停止してプログラムの実行に戻り続けます。つまり、マーキングとプログラムの実行を交互に行います。

最後に、マーク操作が完了した後、ガベージコレクションが完了し、ガベージコレクション操作が完了するまで実行され続けません。これはプログラムが何度も一時停止したようですが、V8全体で最大のゴミコレクションは、メモリが1.5gに達すると、非包括的マーキングの形でのゴミ収集時間が1Sを超えないため、ここでのプログラムの中断は合理的です。さらに、これにより、以前の長い一時停止時間を最大化し、より小さなセクションに直接分割します。これにより、ユーザーエクスペリエンスがより処理されます。

4。V8ガベージコレクションの概要

まず、V8エンジンは現在の主流のJavaScript実行エンジンであることを知る必要があります。 2番目は、内部のガベージコレクションメカニズムによって決定されます。メモリが大きくなるように設定されます。

V8は、世代のリサイクルのアイデアを採用し、記憶を新世代と古い世代に分割します。天の世代と古い世代について、空間とストレージのデータ型は異なります。新世代に64ビットオペレーティングシステムの下で32mのスペースがある場合、32ビットシステムでは16mです。

V8は、さまざまな世代のオブジェクトのごみ収集操作を完了するために、新しい世代のコピーアルゴリズムとタグソートアルゴリズムを使用します。

10。パフォーマンスツールの紹介

GCの目的は、プログラム操作中にメモリスペースをリサイクルできるようにすることです。いわゆる高潔なサイクルの基礎は、実際にコードを書くときに開発者にメモリスペースを合理的に割り当てることを要求することです。ただし、ECMAScriptはプログラマーにメモリスペースを動作させる対応するAPIを提供しないため、GCによってすべて自動的に完了するため、合理的かどうかはわかりません。

プロセス全体のメモリ使用が妥当かどうかを判断したい場合は、メモリの変化に常に注意を払う方法を見つける必要があります。したがって、開発者により多くの監視方法を提供できるようなツールがあり、開発者がプロ​​グラムの操作中にメモリスペースを監視するのに役立ちます。

パフォーマンスを使用することにより、リアルタイムでプログラム実行プロセスのメモリの変更を監視できます。このようにして、プログラムのメモリに問題があるときに問題コードを見つける方法を直接見つけることができます。パフォーマンスツールを使用するための基本的な手順を見てみましょう。

まず、ブラウザを開き、アドレスバーにURLを入力します。最初のレンダリングプロセスを記録するため、アドレスに入力した直後にアクセスすることはお勧めしません。そのため、インターフェイスを開いてURLを入力してください。次に、開発者ツールパネル(F12)を開き、パフォーマンスオプションを選択します。記録関数をオンにし、それをオンにした後、宛先URLにアクセスできます。このページでいくつかの操作を行い、しばらくすると録音を停止します。

メモリ関連の情報を分析できるレポートを取得できます。録音後、いくつかのチャートディスプレイがあり、多くの情報がありますが、これはもっと面倒です。ここでは、主にメモリ関連の情報に焦点を当て、メモリオプション(メモリ)があります。デフォルトでは、チェックされていない場合はチェックする必要があります。ページに青い線が見られます。プロセス全体の私の記憶の変化です。特定の場所で問題がある場合は、増加や減少があるかなど、詳細に観察できます。問題はありません。

1.メモリ問題の症状

プログラムのメモリに問題がある場合、それはどのような形を示しますか?

まず、インターフェイスが荷重や頻繁な一時停止を遅らせている場合、ネットワーク環境を制限することは間違いなく正常です。言い換えれば、メモリを即座に爆発させるコードにはコードが必要です。このようなコードは不適切であり、配置する必要があります。

2つ目は、インターフェイスが継続的な方法で不十分なパフォーマンスを示している場合、つまり、この場合には特に役立ちません。いわゆるメモリの拡張は、現在のインターフェイスの最適な速度を達成するために、特定のメモリ空間に適用される可能性があるという事実を指しますが、このメモリ空間のサイズは、現在のデバイス自体が提供できるサイズをはるかに超えています。

最后,當使用一些界面的時候,如果感知到界面的使用流暢度,隨著時間的加長越來越慢,或者說越來越差,這個過程就伴隨著內存泄露,因為在這種情況下剛開始的時候是沒有問題的,由于我們某些代碼的出現,可能隨著時間的增長讓內存空間越來越少,這也就是所謂的內存泄漏,因此,出現這種情況的時候界面會隨著使用時間的增長表現出性能越來越差的現象。

これは、アプリケーションが実行中にメモリの問題に遭遇する場合です。特定の症状をパフォーマンスと組み合わせて、問題のあるコードを配置すると、変更中にアプリケーションがよりスムーズに表示されます。

2。メモリを監視するいくつかの方法

メモリで発生する問題は、一般に、メモリの漏れ、メモリの拡張、頻繁なゴミ収集の3つのタイプにまとめられています。これらのコンテンツが表示されるとき、それらを定義するためにどの基準を使用する必要がありますか?

メモリの漏れは、実際にはメモリの継続的な増加です。メモリが上昇し続けており、プロセス全体に収まるノードがない場合、プログラムコードにメモリリークがあることを意味します。この時点で、コード内の対応するモジュールを見つける必要があります。

メモリの肥大化は、メモリの膨らみを比較的ファジーにします。それがプログラムであるかデバイスの問題であるかを判断する場合は、より多くのテストを行う必要があります。現時点では、ユーザーに非常に人気のあるデバイスを見つけることができ、アプリケーションを実行し、すべてのデバイスがプロセス全体で非常に悪いパフォーマンスエクスペリエンスを実行する場合です。これは、デバイスではなく、プログラム自体に何か問題があることを意味します。この場合、コードに戻ってメモリの問題を見つける必要があります。

メモリの変更を監視する具体的な方法は何ですか?

ブラウザが提供するタスクマネージャーは、実行中の現在のアプリケーションのメモリの変更を数値的に直接反映できます。 2つ目は、タイムラインタイミングチャートを使用して、このチャートでのアプリケーションの実行中にすべてのメモリの傾向を直接提示することです。さらに、ブラウザにはヒープスナップショットと呼ばれる関数もあります。これは、個別のDomsの存在がメモリ漏れであるため、ターゲットにインターフェイスオブジェクトに別のDomsがあるかどうかを確認できます。

インターフェイスに頻繁なゴミ収集があるかどうかを判断する方法については、現在のメモリトレンドチャートを取得するために異なるツールが必要であり、次に判断を下すために期間分析を実施する必要があります。

3.タスクマネージャーはメモリを監視します

Webアプリケーションの実行中、メモリの変更を観察する場合は、多くの方法があります。ここには、ブラウザのタスクマネージャーを使用して、スクリプトランタイム中にメモリの変更を監視できることを実証します。

インターフェイスに要素を配置し、イベントがトリガーされたら、非常に長い長さの配列を作成します。これにより、メモリスペースの消費が生成されます。

<本文>
    <button id="btn">追加</button>
    <スクリプト>
        const obtn = document.getElementById( 'btn');
        obtn.onclick = function(){
            let arrlist = new Array(1000000)
        }
    </スクリプト>
</本文>

終了したら、ブラウザを開いて実行して、右上隅でより多くのツールを見つけます。

現時点では、デフォルトで現在実行中のスクリプトを見つけることができます。ここで最も心配しているのは、メモリとJavaScriptメモリの2つの列です。

メモリの最初の列は、現在のインターフェイスに多くのDOMノードがあります。

JavaScriptメモリは、このコラムでは、ブラケットの値に注意を払う必要があります。

このインターフェイスを例にとると、ブラケットの値は常に安定した数であり、変更されていないことがわかります。つまり、現在のページにはメモリの成長がありません。この時点で、クリックイベントをトリガーし(ボタンをクリックして)、さらに数回クリックして、完了したら、ブラケットの値が大きくなっていることがわかります。

このプロセスを通じて、現在のブラウザタスクマネージャーを使用して、スクリプトが実行されているときにメモリ全体の変更を監視できます。現在のJavaScriptメモリ列のブラケットの値が増加し続けると、もちろん、このツールは問題を見つけることができず、問題を見つけることができません。

4.タイムラインレコードコンテンツ

以前は、ブラウザのタスクマネージャーを使用してスクリプト実行のメモリの変更を監視できますが、使用中に、そのような操作が現在のスクリプトのメモリに問題があるかどうかを判断するために使用されることがわかります。問題とそれがどのようなスクリプトに関連しているかを見つけたい場合、タスクマネージャーはそれほど使いやすいものではありません。

ここでは、タイムラインを介してメモリの変更を記録する方法を紹介して、どのコードに関連するメモリの問題をより正確に見つける方法、またはその時点でそれが起こったノードを示します。

まず、DOMノードを配置し、クリックイベントを追加し、イベントで多数のDOMノードを作成してメモリ消費をシミュレートし、他のメソッドと組み合わせてアレイを介して非常に長い文字列を形成して、多数のメモリ消費をシミュレートします。

<本文>
    <button id="btn">追加</button>
    <スクリプト>
        const obtn = document.getElementById( 'btn');

        const arrlist = [];

        function test(){
            (i = 0; i < 100000; i++ とします) {
                document.body.appendchild(document.createelement( 'p'))
            }
            arrlist.push(new Array(10000000).Join( 'x'))
        }
        obtn.onclick = test;
    </スクリプト>
</本文>

まず、ブラウザのコンソールツールを開き、デフォルトで実行されていません。つまり、最初にタイミング操作をクリックする必要があります。クリックした後、録音を開始し、数秒待って、[停止]ボタンをクリックします。それを完了すると、チャートが生成されます。

メモリの変更が監視されない場合、メモリをチェックする必要があります。多くの情報が含まれており、いくつかの方法で色の説明を提供します。青いものはjavascriptヒープ、赤いものは現在のドキュメント、緑色のドキュメントはドムノード、茶色のものはリスナー、紫色のドキュメントはCPUメモリです。

簡単に観察するために、JavaScriptのヒープを保持し、他のものをチェックして隠すことができます。このスクリプトの実行中に、JavaScriptヒープのトレンドを見ることができます。現在のツールはタイミングチャートと呼ばれます。つまり、最初の列では、このプロセスでのブランクからレンダリング、最終停止状態までのミリ秒のインターフェイス全体の変化を記録します。必要に応じて、クリックして、現在のインターフェイスフォームを確認できます。

このページが最初に開かれたとき、それは実際には長い間安定した状態にあり、多くの記憶を消費しませんでした。その理由は、クリックがまったくないためです。その後、特定の時点で、記憶は突然上がり、上昇した後、これはADDをクリックした後、間違いなく急増し、急増した後、それは間違いなく安定します。

その後、これはすぐに安定しています。下向きの後、いくつかの小さなフローティングがあります。これは通常のアクティビティであり、頭上です。その後、いくつかの連続したクリックがあり、この連続したクリック動作によりメモリが急上昇する可能性があり、その後、動作しないと再び落ちます。

このようなメモリトレンドチャートを通じて、スクリプトのメモリは非常に安定しており、プロセス全体が上昇してメモリを適用することであり、その減少は通常、それを使用した後にメモリをリサイクルすることです。

メモリのトレンドがまっすぐ上昇していることがわかったら、それは成長するだけでなくリサイクルしないことを意味し、メモリ消費が必要であり、これはメモリリークである可能性が高くなります。上記のタイミングチャートを介して問題を見つけることができます。また、インターフェイスの変更を確認することもできます。また、どのピースがそのようなメモリ問題を引き起こしたかを見つけるために調整することができます。

したがって、タスクマネージャーにとっては、現在のメモリに問題があるかどうかだけでなく、現在のインターフェイスディスプレイと併せて、この問題を発生させるためにどのような操作が行われたかがわかります。

5。分離DOMのヒープスナップショット検索

ここでは、ヒープスナップショット関数の動作の原理を簡単に説明します。写真を撮った後、監視の起源であるすべての情報を見ることができます。ヒープスナップショットは、個別のDOMの検索動作に似ているため、使用する場合に非常に便利です。

インターフェイスで見られる多くの要素は実際にはDOMノードであり、これらのDOMノードは生き残ったDOMツリーに存在する必要があります。ただし、DOMノードにはいくつかの形式があります。1つはガベージオブジェクトであり、もう1つはDOMの分離です。簡単に言えば、このノードがDOMツリーから分離されており、JavaScriptコードで参照されるDOMノードがない場合、ゴミになります。 DOMノードがDOMツリーから分離されているだけでなく、JavaScriptコードにまだ参照がある場合、DOMを分離することです。分離DOMはインターフェイスでは見えませんが、メモリ内のスペースを占有します。

この状況は、ヒープスナップショット機能を介して見つけることができます。

BTNボタンをHTMLに入れて、クリックイベントを追加します。最初に関数にULノードを作成し、ループを使用して、作成後に使用すると、このDOMを参照するためにページに配置する必要があります。

<本文>
    <button id="btn">追加</button>
    <スクリプト>
        const obtn = document.getElementById( 'btn');

        var tmpele;

        関数fn(){
            var ul = document.createelement( 'ul');
            for(var i = 0; i <10; i ++){
                var li = document.createElement('li');
                ul.appendChild(li);
            }
            tmpele = ul;
        }

        obtn.addeventlistener( 'click'、fn);

    </スクリプト>
</本文>

簡単な説明は、ULノードとLIノードを作成することですが、それらはページに配置されていませんが、それらはdomの分離であるJavaScript変数を介して参照されます。

ブラウザのデバッグツールを開き、メモリパネルを選択します。入力後、ヒープスナップショットのオプションを見つけることができます。ここでは、このスナップショットをクリックすることなく、2つの動作テストを行います。

インターフェイスに戻って別の操作を実行し、ボタンをクリックします。クリックした後、別のスナップショット(左側の構成ファイルテキストをクリックすると、写真インターフェイスが表示されます)を使用するか、以前と同じ操作を行い、DETAを検索します。

今回は、Snapshot 2で検索したことがわかります。これらがコードで作成されたDOMノードであり、インターフェイスに追加されていないことは明らかですが、ヒープには存在します。これは実際にはスペースの無駄です。

関数fn(){
    var ul = document.createelement( 'ul');
    for(var i = 0; i <10; i ++){
        var li = document.createElement('li');
        ul.appendChild(li);
    }
    tmpele = ul;
    // domをクリアします
    ul = null;
}

ここでは、ブラウザで提供されるヒープスナップショットと呼ばれる関数を使用して、現在のヒープの写真を撮ったことを簡単に要約します。

分離DOMはページに反映されておらず、メモリに存在するため、現時点ではメモリの無駄です。

6.頻繁にGCがあるかどうかを判断します

ここでは、現在のWebアプリケーションの実行中に頻繁にゴミ収集があるかどうかを判断する方法について説明します。 GCが機能するときにアプリケーションが停止します。したがって、頻繁にGC作業はWebアプリケーションに友好的ではありません。なぜなら、それは死んだ状態になり、ユーザーは立ち往生すると感じるからです。

現時点では、現在のアプリケーションの実行中に頻繁にゴミ収集があるかどうかを判断する方法を見つける必要があります。

ここには、タイムラインのタイミングチャートの傾向を判断し、パフォーマンスツールパネルの現在のメモリトレンドを監視する方法が2つあります。青いトレンドバーが頻繁に上昇して上昇することがわかった場合。それは頻繁なゴミ収集を意味します。このような状況が発生した後、対応する時間ノードを見つけてから、そのような現象を引き起こしてコードで処理するためにどのような操作が行われたかを確認する必要があります。

通常、インターフェイスのレンダリングが完了した後、domノードメモリとJavaScriptメモリの両方が変更されているか、変更が非常に少ないため、数値の変化であるため、判断を下すとタスクマネージャーが簡単になります。ここで頻繁にGC操作がある場合、この値の変更は即座に増加して即座に減少するため、そのようなプロセスを見ることは、コードに頻繁にゴミ収集操作があることを意味します。

頻繁なゴミ収集操作の明らかな影響は、ユーザーが内部の観点から使用すると非常にst音があると感じていることです。

要約する

これは、JavaScriptのガベージコレクションメカニズムのより関連するコンテンツについては、この記事の最後の記事を検索してください。

以下もご興味があるかもしれません:
  • JavaScriptのゴミ収集メカニズムとメモリ管理について私から学ぶ
  • JavaScriptガベージコレクションメカニズム分析
  • JSゴミ収集メカニズムの理解
  • JavaScript のガベージコレクションメカニズムとメモリ管理
  • JavaScriptのガベージコレクションの仕組みについて話す
  • js クロージャとガベージ コレクション メカニズムの例の詳細な説明

<<:  VMware Workstation Pro 16 グラフィックチュートリアル (CentOS8 仮想マシン クラスタの構築)

>>:  MySQL 8.0 アトミック DDL 構文の詳細な説明

推薦する

少なくとも7日間連続して注文を行ったユーザーに対するSQLクエリ

テーブルを作成するテーブル order(id varchar(10),date datetime,o...

MySQL は、あるテーブルのデータに基づいて別のテーブルの特定のフィールドを更新します (SQL ステートメント)

次のコードは、MySQL が 1 つのテーブルのデータに基づいて別のテーブルのいくつかのフィールドを...

JSでよく使われるデータ処理方法

目次DOM処理配列方法要約するDOM処理DOM はドキュメントの構造化された表現を提供し、スクリプト...

MySQLは、where in()順序ソートを実装するためにfind_in_set()関数を使用します。

この記事では、MySQL で find_in_set() 関数を使用して where in() の順...

React dva実装コード

目次ドヴァdvaの使用DVAの実装非同期をサポートルーターの実装成し遂げる:ドヴァdva は、red...

Javascript サンプル プロジェクトでの虫眼鏡効果の実装プロセス

目次序文事例: JD.com の虫眼鏡効果の模倣オフセットシリーズクライアントシリーズスクロールシリ...

MySQLデータベーステーブルの容量を確認する方法の例

この記事では、MySQL のデータベース テーブルの容量を確認するためのコマンド ステートメントを紹...

LeetCode の SQL 実装 (182. 重複するメールボックス)

[LeetCode] 182.重複メールPerson という名前のテーブル内のすべての重複メールを...

Linux スワップ パーティション (詳細説明)

目次リナックス1. SWAPとは2. swappiness は何を調節しますか? 3. スワップ操作...

Dockerがコンテナサービスを停止または削除できない問題の解決策

序文今日、開発者から、コンテナ サービスを停止、rm (docker rm -f)、または強制終了で...

垂直方向の中央揃えをエレガントに実現する方法を教えます(推奨)

序文CSS で水平方向と垂直方向に中央揃えする方法はたくさんあります。この記事で紹介する方法は非常に...

Vueのログインとログアウトの詳細な説明

目次ログインビジネスプロセスログイン機能の実装要約するまず、エフェクトの実装プロセスを見てみましょう...

Alibaba Cloud イメージリポジトリの Docker 構成変更の実装

docker リポジトリ自体は非常に遅いですが、中国の Alibaba Cloud ミラー リポジト...

ソフトウェア テスト - MySQL (VI: データベース関数)

1.MySQL関数1. 数学関数PI() # 円周率 (pi) の値を返します。デフォルトの小数点...

共有サイドバーを実装するためのネイティブJS

この記事では、ネイティブ JS で実装された共有サイドバーを紹介します。効果は次のとおりです。 以下...