JavaScriptでのゴミ回収とメモリ漏洩


前言
プログラムの実行にはメモリが必要です.プログラムが要求する限り、オペレーティングシステムまたは実行時にメモリを供給する必要があります.メモリ漏洩とは簡単には使わないメモリで、タイムリーに解放されていません.メモリ漏洩を回避するために、Javascriptのゴミ回収メカニズムを紹介します.
CやC++などの言語では,開発者がメモリの申請や回収を直接制御できる.しかしJava,C#,JavaScript言語では,変数のメモリ空間の申請や解放はプログラム自身で処理されており,開発者は関心を持つ必要はない.つまりJavascriptには自動ゴミ回収メカニズム(Garbage Collection)がある.
もっと良質な文章を読みたいなら、GitHubブログを突き刺してください.年間50編の良質な文章が待っています.
一、ゴミ回収の必要性
次の言葉は「JavaScript権威ガイド(第4版)」から引用されています.
文字列、オブジェクト、配列には固定サイズがないため、サイズが既知である場合に動的なストレージ割り当てを行うことができます.JavaScriptプログラムは、文字列、配列、またはオブジェクトを作成するたびに、そのエンティティを格納するためにメモリを割り当てる必要があります.このように動的にメモリが割り当てられている限り、最終的にはこれらのメモリを解放して再利用できるようにしなければなりません.そうしないと、JavaScriptの解釈器はシステムで使用可能なすべてのメモリを消費し、システムをクラッシュさせます.
この言葉は、なぜシステムがゴミ回収を必要とするのかを説明しています.JavaScriptはC/C++とは異なり、独自のゴミ回収メカニズムを持っています.
JavaScriptのゴミ回収のメカニズムは簡単です.使用されなくなった変数を見つけて、使用したメモリを解放します.しかし、このプロセスは時々ではありません.オーバーヘッドが大きいので、ゴミ回収器は一定の時間間隔で周期的に実行されます.
var a = "    ";
var b = "    ";
var a = b; //  a

このコードが実行されると、「波の中の舟」という文字列は参照を失い(以前はa参照されていた)、システムはこの事実を検出すると、文字列の記憶空間を解放して再利用することができる.
二、ゴミ回収メカニズム
ごみの回収メカニズムはどのように知っていて、どれらのメモリはもう必要ありませんか?
ごみ回収には、タグクリア、参照カウントの2つの方法があります.参照カウントはあまり一般的ではありませんが、タグクリアは一般的です.
1.タグクリア
これはjavascriptで最もよく使われるゴミ回収方法です.変数が実行環境に入ると、この変数は「環境に入る」とマークされます.論理的には、ストリームが対応する環境に入ると使用される可能性があるため、環境に入る変数によって消費されるメモリは解放されません.変数が環境から離れる場合は、「環境から離れる」とマークします.
ゴミ収集器は、実行時にメモリに格納されているすべての変数にタグを付けます.その後、環境内の変数と、環境内の変数によって参照されるタグが削除されます.その後、タグ付けされた変数は、環境内の変数がこれらの変数にアクセスできないため、削除の準備をしている変数と見なされます.最後に.ゴミ収集器はメモリの消去を完了し、タグ付きの値を破棄し、使用したメモリ領域を回収します.
この方法を例に挙げて説明します.
var m = 0,n = 19 //   m,n,add()        。
add(m, n) //   a, b, c       。
console.log(n) // a,b,c       ,      。
function add(a, b) {
  a++
  var c = a + b
  return c
}

2.参照数
「参照カウント」とは、言語エンジンに「参照テーブル」があり、メモリ内のすべてのリソース(通常は様々な値)の参照回数が保存されていることを意味します.1つの値の参照回数が0の場合、この値は使用されなくなるため、このメモリを解放できます.
上の図では、左下の2つの値には、参照がないので解放できます.
値が不要になった場合、参照数が0ではないため、ゴミ回収メカニズムでメモリが解放されず、メモリが漏洩します.
var arr = [1, 2, 3, 4];
arr = [2, 4, 5]
console.log('    ');

上のコードでは、配列[1,2,3,4]は値であり、メモリを占有します.変数arrはこの値への参照のみであるため、参照回数は1である.後続のコードはarrを使用していませんが、メモリを消費し続けます.メモリを解放する方法については、後述します.
3行目のコードでは、配列[1,2,3,4]が参照する変数arrがまた別の値を取得すると、配列[1,2,3,4]の参照回数が1減少し、このとき参照回数が0になると、この値にアクセスできないことを示し、その占めた内蔵空間を回収することができる.
しかし、リファレンス数には最大の問題があります.ループリファレンス
function func() {
    let obj1 = {};
    let obj2 = {};

    obj1.a = obj2; // obj1    obj2
    obj2.a = obj1; // obj2    obj1
}

関数funcの実行が終了すると、戻り値はundefinedとなるため、関数全体および内部の変数は回収されるべきであるが、参照カウント方法によればobj 1およびobj 2の参照回数は0ではないため、回収されない.
ループ参照の問題を解決するには、使用しないときに手動で空に設定することが望ましい.上記の例では、
obj1 = null;
obj2 = null;

三、メモリの漏洩を引き起こす状況は何ですか?
JavaScriptは自動的にゴミ収集を行いますが、私たちのコードの書き方が適切でないと、変数は常に「環境に入る」状態になり、回収できません.メモリの漏洩に関する一般的な状況を以下に示します.
1.予期せぬグローバル変数
function foo(arg) {
    bar = "this is a hidden global variable";
}

barは宣言されず、グローバル変数になり、ページが閉じるまで解放されません.
別の予期せぬグローバル変数は、thisによって作成されます.
function foo() {
    this.variable = "potential accidental global";
}
// foo     ,this        (window)
foo();

JavaScriptファイルのヘッダに「use strict」を付けると、このようなエラーを回避できます.厳密モード解析JavaScriptを有効にし、予期せぬグローバル変数を回避します.
2.忘れられたタイマーまたはコールバック関数
var someResource = getData();
setInterval(function() {
    var node = document.getElementById('Node');
    if(node) {
        //    node   someResource
        node.innerHTML = JSON.stringify(someResource));
    }
}, 1000);

このようなコードはよく見られるが、idがノードの要素がDOMから除去されると、このタイマは依然として存在する.また、コールバック関数にsomeResourceへの参照が含まれているため、タイマの外のsomeResourceも解放されない.
3.閉パッケージ
function bindEvent(){
  var obj=document.createElement('xxx')
  obj.onclick=function(){
    // Even if it is a empty function
  }
}

閉包は、関数内の局所変数を維持し、解放されないようにすることができる.上記の例では、イベントコールバックを定義する場合、関数内定義関数であり、内部関数であるイベントコールバックが外部関数を参照するため、閉パケットが形成されます.
//             
function bindEvent() {
  var obj = document.createElement('xxx')
  obj.onclick = onclickHandler
}
//                  ,   dom   
function bindEvent() {
  var obj = document.createElement('xxx')
  obj.onclick = function() {
    // Even if it is a empty function
  }
  obj = null
}

解決策として、イベント処理関数を外部に定義したり、閉パッケージを解除したり、イベント処理関数を定義する外部関数でdomへの参照を削除したりします.
4.クリーンアップされていないDOM要素参照
DOMノード内部のデータ構造を保存するのに役立つ場合があります.表の数行の内容をすばやく更新したい場合は、各行のDOMを辞書(JSONキー値ペア)または配列に保存することは意味があります.この場合、同じDOM要素には2つの参照があります.1つはDOMツリーにあり、もう1つは辞書にあります.将来、これらの行を削除することにした場合は、2つの参照をクリアする必要があります.
var elements = {
    button: document.getElementById('button'),
    image: document.getElementById('image'),
    text: document.getElementById('text')
};
function doStuff() {
    image.src = 'http://some.url/image';
    button.click();
    console.log(text.innerHTML);
}
function removeButton() {
    document.body.removeChild(document.getElementById('button'));
    //   ,          #button    
    // elements   。button         ,    GC   。
}

removeChildでbuttonを削除しましたが、elementsオブジェクトには#buttonの参照が保存されています.言い換えれば、DOM要素はまだメモリの中にあります.
四、メモリ漏れの識別方法
新しいバージョンのchromeはperformanceで表示されます.
手順:
  • 開発者ツールPerformance
  • を開く
  • Screenshotsとmemory
  • をチェック
  • 左上隅小円点録画開始
  • 録画停止
  • 図中のHeapに対応する部分では、メモリが周期的に下落したり、ゴミ回収の周期が見えたりしますが、ゴミ回収後の最低値(minと呼ばれています)がminが上昇し続けている場合は、深刻なメモリ漏れの問題があるに違いありません.
    メモリの漏洩を回避する方法:
  • 不要なグローバル変数、またはライフサイクルの長いオブジェクトを減らす、不要なデータを速やかにゴミ回収する
  • .
  • プログラムロジックに注意し、「デッドサイクル」などの
  • を避ける
  • オブジェクトの作成を避ける
  • 要するに、使わないものはすぐに返さなければならないという原則に従う必要があります.
    五、ゴミ回収の使用シーンの最適化
    1.配列array最適化
    []を配列オブジェクトに割り当てるのは、配列を空にする近道です(arr=[];).しかし、この方法で新しい空のオブジェクトが作成され、元の配列オブジェクトが小さなメモリゴミになったことに注意してください.実際には,配列長を0(arr.length=0)に割り当てることで,配列を空にする目的も達成でき,配列再利用を同時に実現し,メモリゴミの発生を低減できる.
    const arr = [1, 2, 3, 4];
    console.log('    ');
    arr.length = 0  //          ,        。
    // arr = [];    a        ,                 。

    2.対象をできるだけ多重化する
    オブジェクトはできるだけ多重化し、特にループなどの場所で新しいオブジェクトが作成され、多重化できると多重化します.使わない対象は、できるだけnullに設定して、できるだけ早くゴミに回収されます.
    var t = {} //              。
    for (var i = 0; i < 10; i++) {
      // var t = {};//              。
      t.age = 19
      t.name = '123'
      t.index = i
      console.log(t)
    }
    t = null //         ,       null;      。

    3.ループ内の関数式は、多重化できるようにループの外に置くことが望ましい.
    //                。
    for (var k = 0; k < 10; k++) {
      var t = function(a) {
        //    10       。
        console.log(a)
      }
      t(k)
    }
    //     
    function t(a) {
      console.log(a)
    }
    for (var k = 0; k < 10; k++) {
      t(k)
    }
    t = null

    みんなに1つの使いやすいBUGの監視するツールFundebugを推薦して、無料で試用することを歓迎します!
    公衆番号に注目してください:先端の職人、あなたの成長は私たちと一緒に目撃します!
    参考資料
  • 珠峰アーキテクチャ課(強く推奨)
  • JavaScriptプレミアムインサイダー(jsプレミアム)
  • JavaScriptごみ回収メカニズム
  • JavaScriptメモリリークチュートリアル
  • JavaScript権威ガイド(第4版)
  • JavaScriptでのゴミ回収
  • 4 JavaScriptメモリの漏洩および
  • を回避する方法