処理方法: 1. 使用後に null を割り当てるか、他の値を再割り当てする; 2. 最新のガベージ コレクション アルゴリズムを使用する; 3. DOM 要素の参照を保存するときに注意する; 4. SessionStack を渡す、アプリケーションでの再生の問題メモリ リークを回避し、アプリケーション全体のメモリ使用量を増やします。
このチュートリアルの動作環境: Windows7 システム、JavaScript バージョン 1.8.5、Dell G3 コンピューター。 ,
メモリ リークは、すべての開発者が最終的に直面する問題であり、応答の遅さ、クラッシュ、長い遅延、その他のアプリケーションの問題など、多くの問題の原因となります。
基本的に、メモリ リークは、アプリケーションがメモリを占有する必要がなくなったときに、何らかの理由でメモリがオペレーティング システムまたは利用可能なメモリ プールによって再利用されないことと定義できます。プログラミング言語によってメモリの管理方法が異なります。どのメモリが不要になり、オペレーティング システムによって再利用できるかは、開発者だけが最もよく知っています。一部のプログラミング言語は、開発者がこの種のことを行うのに役立つ言語機能を提供します。メモリが必要かどうかを開発者に明確にしてもらうことに依存している人もいます。
JavaScript はガベージ コレクション言語です。ガベージ コレクション言語は、以前に割り当てられたメモリが到達可能かどうかを定期的にチェックすることで、開発者がメモリを管理するのに役立ちます。言い換えれば、ガベージ コレクション言語は、「メモリがまだ利用可能である」および「メモリがまだ到達可能である」問題を軽減します。 2 つの違いは微妙ですが重要です。どのメモリが今後も使用されるかは開発者だけが知っていますが、到達不能なメモリはアルゴリズムによって決定およびマークされ、オペレーティング システムによって即座に回収されます。
ガベージ コレクション言語におけるメモリ リークの主な原因は、不要な参照です。それを理解する前に、ガベージ コレクション言語が到達可能なメモリと到達不可能なメモリをどのように区別するかを理解する必要があります。
ほとんどのガベージ コレクション言語で使用されるアルゴリズムは、マーク アンド スイープと呼ばれます。アルゴリズムは次のステップで構成されます。
ガベージ コレクターは「ルート」のリストを作成します。ルートは通常、コード内のグローバル変数への参照です。 JavaScript では、「ウィンドウ」オブジェクトはグローバル変数であり、ルートとして扱われます。 window オブジェクトは常に存在するため、ガベージ コレクターは、ウィンドウ オブジェクトとそのすべての子オブジェクトが存在する (つまり、ガベージではない) ことを確認できます。
すべてのルートがチェックされ、アクティブとしてマークされます (つまり、ガベージではありません)。ゴミ)ゴミではありません)。すべてのサブオブジェクトも再帰的にチェックされます。ルートから始まるすべてのオブジェクトが到達可能であれば、それらはガベージとはみなされません。
マークされていないメモリはすべてガベージとして扱われるため、コレクタはメモリを解放してオペレーティング システムに返すことができるようになります。
最新のガベージ コレクターはアルゴリズムが改良されていますが、本質は同じです。到達可能なメモリがマークされ、残りはガベージ コレクションされます。
不必要な参照とは、メモリ参照がもう必要ないことを開発者が認識しているにもかかわらず、何らかの理由でメモリ参照がアクティブなルート ツリーにまだ残っていることを意味します。 JavaScript では、不要な参照とは、コード内に残り、不要になったが、解放される必要があるメモリの一部を指す変数です。これは開発者のミスだと考える人もいます。
JavaScript で最も一般的なメモリ リークを理解するには、参照がどのように忘れられやすいかを理解する必要があります。
JavaScript は、未定義の変数をより緩やかな方法で処理します。グローバルオブジェクト内の新しい変数。ブラウザでは、グローバル オブジェクトは window
です。
真実は次のとおりです:
関数 foo
が内部で var
を使用するのを忘れ、誤ってグローバル変数を作成しました。この例では単純な文字列がリークされており、無害ですが、さらに悪いことがあります。
別の予期しないグローバル変数が this
によって作成される可能性があります:
JavaScript ファイルの先頭に
'use strict'
を追加します。このようなエラーの発生を回避できます。 JavaScript の厳密モード解析を有効にして、予期しないグローバル変数を回避します。
グローバル変数に関する注意事項
いくつかの予期せぬグローバル変数について説明しましたが、明示的なグローバル変数によって生成されるガベージがまだいくつかあります。これらは、(空または再割り当てとして定義されていない限り) リサイクル不可として定義されます。大量の情報を一時的に保存および処理するためにグローバル変数を使用する場合は特に注意が必要です。大量のデータを保存するためにグローバル変数を使用する必要がある場合は、必ず null に設定するか、使用後に再定義してください。グローバル変数に関連するメモリ消費量増加の主な原因の 1 つはキャッシュです。データのキャッシュは再利用するためのものであり、有効に使用するにはキャッシュのサイズに上限を設ける必要があります。メモリ消費量が多いと、キャッシュされたコンテンツを再利用できないため、キャッシュが上限を超えてしまいます。
JavaScript で setInterval
を使用することは非常に一般的です。一般的なコード部分:
この例が示していること: ノードまたはデータに関連付けられたタイマーは不要になり、node
オブジェクトは削除でき、コールバック関数全体が不要になります。ただし、タイマー コールバック関数はまだリサイクルされていません (タイマーが停止するまでリサイクルされません)。同時に、大量のデータが保存されている場合、someResource
はリサイクルできません。
オブザーバーの場合、オブザーバーが不要になったら (または、関連付けられたオブジェクトに到達できなくなったら)、明示的に削除することが重要です。古い IE 6 は循環参照を処理できません。現在、ほとんどのブラウザは、オブザーバー オブジェクトが到達不能になった場合、明示的に削除しなくても、オブザーバー ハンドラーをリサイクルできます。
オブザーバー コードの例:
オブジェクト オブザーバーと循環参照に関する考慮事項
古いバージョンの IE では、DOM ノードと JavaScript コードを検出できません。メモリリークの原因となります。現在、最新のブラウザー (IE および Microsoft Edge を含む) は、より高度なガベージ コレクション アルゴリズムを使用しており、循環参照を正しく検出して処理できます。つまり、ノード メモリをリサイクルするときに、removeEventListener
を呼び出す必要はありません。
DOM ノードの内部データ構造を保存すると便利な場合があります。テーブルの複数の行をすばやく更新したい場合は、DOM の各行をディクショナリ (JSON キーと値のペア) または配列として保存するのが合理的です。この時点で、同じ DOM 要素への参照が 2 つあります。1 つは DOM ツリー内に、もう 1 つはディクショナリ内にあります。将来これらの行を削除する場合は、両方の参照をクリアする必要があります。
さらに、DOM ツリーまたはサブノード内の参照の問題も考慮する必要があります。 JavaScript コードがテーブル内の <td>
への参照を保存しているとします。将来テーブル全体を削除することにした場合、GC は保存された <td>
以外の他のノードをリサイクルすると直感的に思います。これは実際の状況ではありません。この <td>
はテーブルの子ノードであり、子要素は親要素と参照関係を持っています。コードは <td>
への参照を保持しているため、テーブル全体がメモリ内に残ります。 DOM 要素への参照を保存するときは注意してください。
クロージャは JavaScript 開発の重要な側面です。匿名関数は親スコープ内の変数にアクセスできます。
コード例:
コード スニペットは 1 つのことを実行します。replaceThing
が呼び出されるたびに、theThing
は大きな配列とnew クロージャの新しいオブジェクト (someMethod
)。同時に、変数 unused
は、originalThing
(前の replaceThing
は theThing
と呼ばれます) を参照するクロージャです。あなたの考えは混乱していますか?最も重要なことは、クロージャのスコープが作成されると、それらは同じ親スコープを持ち、スコープが共有されることです。 someMethod
は theThing
を通じて利用でき、someMethod
は、unused
が決して存在しないにもかかわらず、クロージャ スコープを unused
と共有します。 used の場合、それが参照する originalThing
により、メモリ内に強制的に残ります (リサイクルされなくなります)。このコードを繰り返し実行すると、メモリ使用量が増加し続け、ガベージ コレクター (GC) がメモリ使用量を削減できないことがわかります。基本的に、クロージャのリンクされたリストが作成されており、各クロージャ スコープには大きな配列への間接参照が含まれているため、深刻なメモリ リークが発生します。
Meteor のブログ投稿では、この問題の解決方法について説明しています。
replaceThing
の最後にoriginalThing = null
を追加します。
Chrome は、JavaScript のメモリ使用量を検出するための優れたツール セットを提供します。メモリに関連する 2 つの重要なツール: timeline
と profiles
。
タイムラインは、コード内の不要なメモリを検出できます。このスクリーンショットでは、リークされた可能性のあるオブジェクトが着実に増加していることがわかります。データ収集の終了時には、メモリ使用量が収集の開始時よりも大幅に増加し、ノードの総数も非常に多くなります。コード内に DOM ノードのリークがあることを示すさまざまな兆候があります。
プロファイルは、多くの時間を集中的に費やすことができるツールです。スナップショットを保存し、JavaScript コードのメモリ使用量のさまざまなスナップショットを比較し、記録することもできます。時間配分。各結果にはさまざまな種類のリストが含まれており、メモリ リークに関連するリストは概要リストと比較リストです。
summary (概要) リストには、さまざまなタイプのオブジェクトの割り当てと合計サイズが表示されます。浅いサイズ (特定のタイプのすべてのオブジェクトの合計サイズ)、保持サイズ (浅いサイズと関連するその他のサイズ)。それ)オブジェクトのサイズ)。また、オブジェクトが関連する GC ルートからどのくらい離れているかについてのアイデアも提供されます。
さまざまなスナップショットの比較リストを比較して、メモリ リークを見つけます。
リークには、基本的に 2 つのタイプがあります。周期的なメモリ増加によって引き起こされるリークと、時折発生するメモリ リークです。明らかに、周期的なメモリ リークは見つけやすいですが、時折発生するメモリ リークはより困難で、一般に無視されやすいです。時折発生するメモリ リークは最適化の問題と考えられますが、再発するメモリ リークは解決する必要があるバグと考えられます。
Chrome ドキュメントのコードを例として挙げます。
grow
が実行されると、p 個のノードが作成されて DOM に挿入され、巨大なノードが割り当てられます。グローバル変数の配列。メモリの着実な増加は、上記のツールを使用して検出できます。
timeline タグはこれを行うのに適しています。 Chrome でサンプルを開き、開発ツールを開き、タイムラインに切り替え、メモリを確認して記録ボタンをクリックし、ページ上の The Button
ボタンをクリックします。しばらくしてから記録を停止し、結果を確認します。
画像内のノード (緑の線) と JS ヒープ (青の線) の 2 つの兆候は、メモリ リークがあることを示しています。ノードは着実に成長しており、減少していません。これは重要な兆候です。
JS ヒープのメモリ使用量も着実に増加しています。ガベージコレクターがあるため、見つけるのはそれほど簡単ではありません。この図はメモリ使用量が増減していることを示しており、実際、メモリ使用量が低下するたびに、JS ヒープのサイズが以前よりも大きくなっています。つまり、ガベージ コレクターはメモリの収集を続けますが、メモリは依然として定期的にリークされます。
メモリ リークがあることを確認した後、根本原因を特定します。
Chrome Dev Tools の [プロファイル] タブに切り替えてページを更新し、ページの更新が完了したら、[ヒープ スナップショットの取得] をクリックしてスナップショットを保存します。ベースラインとしてのスナップショット。次に、[The Button
] ボタンを再度クリックし、数秒待ってから 2 番目のスナップショットを保存します。
フィルター メニューで [概要] を選択し、右側の [スナップショット 1 とスナップショット 2 の間に割り当てられたオブジェクト] を選択するか、フィルター メニューで [比較] を選択すると、比較リストが表示されます。
この例ではメモリ リークを簡単に見つけることができます。(string)
Size Delta
コンストラクター、8MB、58 個の新しいオブジェクトを見てください。新しいオブジェクトは割り当てられますが、解放されず、8MB を占有します。
(string)
コンストラクターを展開すると、多数の個別のメモリ割り当てが表示されます。個別の割り当てを選択すると、次のリテイナーに注目が集まります。
選択した割り当ては、window
オブジェクトの xx
変数に関連付けられた配列の一部です。これは、巨大なオブジェクトからリサイクルできないルート (window
) までの完全なパスを示しています。潜在的な漏れとその原因を特定しました。
この例は比較的単純で、少数の DOM ノードのみがリークされます。これは、前述のスナップショットを使用して簡単に見つけることができます。大規模なサイトの場合、Chrome はヒープ割り当ての記録機能も提供します。
Chrome Dev Tools の [プロファイル] タブに戻り、[ヒープ割り当てを記録] をクリックします。ツールの実行中、メモリ割り当てを表す上部の青いバーに注目してください。毎秒大量のメモリ割り当てが行われます。数秒間実行されてから停止します。
上の図でこのツールの切り札を確認できます。特定のタイムラインを選択すると、この期間中のメモリ割り当てが表示されます。可能な限りピークに近いタイムラインを選択すると、以下のリストには 3 つのコンストラクターのみが表示されます。1 つは最もリークされた (string)
、次は関連する DOM 割り当て、最後のコンストラクターは Text
コンストラクター (DOM リーフ ノードに含まれるテキスト)。
リストから HTMLpElement
コンストラクターを選択し、割り当てスタック
を選択します。
これで、要素がどこに割り当てられているかがわかりました (grow
-> createSomeNodes
)。図のタイムラインを注意深く見て、HTMLpElement# # を見つけてください。 # コンストラクターは何度も呼び出されます。これは、メモリが占有されており、GC によってリサイクルできないことを意味します。これらのオブジェクトが割り当てられる正確な場所はわかっています (
createSomeNodes)。コード自体に戻って、メモリ リークを修正する方法について説明します。
grow と
createSomeNodes が表示されます。
grow が選択されている場合、関連するオブジェクト コンストラクターを見ると、
(string)、
HTMLpElement、および
Text がリークしていることがわかります。
JavaScript 上級チュートリアル]
以上がJavaScriptのメモリリークに対処する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。