メモリーリークパターンを理解する

モリーリークに関する覚え書き

  • モリーリークとは
    • コンピュータの動作中に、使用可能なメモリ容量が、だんだん減っていく現象。
    • OSやアプリケーションソフトが、処理のために占有したメモリ領域を、解放しないまま放置してしまうために起きる。
  • モリーリークパターン(以下のタイプを考察)
    • ドキュメントツリーに属さないノードに、イベントを貼り付けた場合。
    • DOM プロセッサが管理しているオブジェクトと、スクリプトエンジンが管理しているオブジェクト、が混在した場合
    • いわゆるクロージャが、その混在を招くパターン(これが難解)

ドキュメントツリーに属さないノードに、イベントを貼り付けた場合

  • ページを破棄するときに、ドキュメントツリーのノードをたどってイベントを切り離されるが、それに属していないと、削除されない。
  • リークするメモリは数バイト程度なので、少量なら無視できる
//createElement で作ったノードに、イベントを貼り付けただけのコード
function hoge ( ) {
  var e = document.createElement( 'div' );
  e.onclick = function () { ; };
}

DOM プロセッサが管理しているオブジェクトと、
 スクリプトエンジンが管理しているオブジェクトが違う場合

// ノードのオリジナルなプロパティに、オブジェクトを追加するコード
var e = document.getElementById( 'hoge' );
e.fuga = function ( ) { ; };
  • 変数 (e) 参照元( hoge )は、 DOM プロセッサが管理しているオブジェクトで、それに付け足した .fuga は、スクリプトエンジンが管理しているオブジェクトである。
    これが、メモリーリークを招くパターンである

(変数に代入されたノードに、勝手なプロパティを付けてはならない。)

  • 一見、循環しているようでも、スクリプトエンジンが管理しているオブジェクトなので、リークしない。
    (obj.fugaには、objの参照アドレスが保存されるだけだろうから、と勝手な思い込み)
// スクリプトエンジンが管理しているオブジェクトが循環するコード
var obj = new Object;
obj.fuga = obj;

いわゆるクロージャが問題になるパターン

function hoge ( id ) {
  var e = document.getElementById( id );
  e.onclick = function ( evt ) {
    alert( e.id );
    // 変数 e を保持
 };
}
  • クロージャになるコード2(以後、理解しやすいのでこちらを使う)
function hoge ( id ) {
  var e = document.getElementById( id );
  var f = function ( evt ) { alert( e.id ); };

  e.onclick = f;
}


どのようなことがおこって、問題になるのか?

  • まず、これを理解しやすいように色分けをすることにする。
  • モリーリークは、循環参照しているオブジェクトの管轄が異なる場合に発生する
    つまり、DOM プロセッサが管理しているオブジェクトから、スクリプトエンジンが管理しているオブジェクトに、そしてまた DOM プロセッサが管理しているオブジェクトにと、循環参照が成立した時にリークする。
    (オブジェクトの管理者が同じなら、循環参照しても問題はない)
(function outerFunc() {
  var e = document.getElementById( 'hoge' );
  var innerFunc = function ( evt ) {
    alert( e.nodeName );
  };
  e.onclick = innerFunc;
})();
    • e(DOM) onclick(DOM) Function(script)e(DOM)と、
      管轄が異なるオブジェクトの循環参照が成立する。
    • 色で表現すれば、となったときに、循環参照する。(メモリーリークとなる)
  • 一見すると循環参照していないようなので、勘違いしてしまう場合
(function outerFunc() {
  var e = document.getElementById( 'hoge' );
  var innerFunc = function ( evt ) {
    var element = evt.target;
    alert( element.nodeName );
  };
  e.onclick = innerFunc;
})();
    • 関数 (outerFunc) の変数 (e) は、 onclick のイベントが発生すると、関数 (innerFunc) が実行されるようになっている。しかし、そこには、外側で使われている変数 (e)が使われていない。
      だから循環参照になっていない」と考えるのは、間違いである。
    • 関数 (innerFunc) の element と 関数 (outerFunc) の変数 (e)は、同じノード(hoge)を参照している。なのでこれが循環参照でリークしている考えるのも間違いである。
    • いわゆるクロージャを生成した時点で、関数 (innerFunc) は、変数 (e) を、いつでも参照できるようになっている。なので循環参照が成立する
    • 色で表現すると。
      e(DOM)onclick(DOM)innerFunc(script)e(DOM) が無くても参照可能になっている!
  • モリーリークを断ち切ってしまう
(function outerFunc() {
  var e = document.getElementById( 'hoge' );
  var innerFunc = function ( evt ) {
    alert( e.nodeName );
  };
  e.onclick = innerFunc;
  e = null; // これをスクリプトが管理しているオブジェクトにしてしまう
})();
    • もう解説は、いらないだろう。
  • 以下は、リークしない
(function outerFunc() {
  var e = 'hoge';
  var innerFunc = function ( evt ) {
    var element = document.getElementById( e );
    alert( element.nodeName );
  };

  document.getElementById( e ).onclick = innerFunc;
})();
    • document.getElementById( e ) の、onclick で呼ばれる、関数 (innerFunc) から参照できる変数 (e) は、スクリプトエンジンが管理しているオブジェクトである。
    • 色で表現すると、

document.getElementById( e )onclick(DOM)innerFunc(script)e(DOM) が無くても参照できる!

  • 一見すると循環参照していないようなので、勘違いしてしまう場合 その2
(function outerFunc() {
  var e = 'hoge';
  var d = document; //←これを追加

  var innerFunc = function ( evt ) {
    var element = document.getElementById( e );
    alert( element.nodeName );
  };

  document.getElementById( e ).onclick = innerFunc;
})();
    • document.getElementById( e ) の、onclick で呼ばれる、関数 (innerFunc) から参照できる変数 (d) は、グローバル変数document が、保存されているが、変数 (d) 自体は、ローカル変数なので、参照することができる。なのでメモリーリークする。
    • 色で表現すると。 document.getElementById( e ) onclick(DOM) innerFunc(script)d(DOM)
function hoge() {
 document.getElementById('hoge').onclick = function(e) {
    alert( 'abc' ); 
 };
}
    • 外側に参照可能な、DOM プロセッサが管理がしているローカル変数がない
  • 下記も同様。
function hoge() {
  var nodes = document.getElementsByTagName('A');
 var I = nodes.length;
 var i;
 for (i = 0; i < I; i++) {
   nodes[i].onclick = function() {
   // 変数 nodes、I、i を保持
  };
 }
}
    • まだまだ続く予定

結局これがしたいのだが

  • これはまだ、書きかけです
  • 下は何がしたいのかというと、イベントを貼り付ける node の親からのitem番号でそのnodeを

見つけて、その生きたリストに貼り付ける。それならメモリーリークしない!?

  • あぁ〜この疑問符がとれるのは、いつのことやら・・・
function addEvent ( element, evt, eventHandler, flag ) {
  var cnt = 0, n = element, t;
  if( element != document ) {
    while( n = n.previousSibling ) cnt++;
    t = element.parentNode;
    t.item( cnt )./*@if( @_jscript ) attachEvent( 'on' + @else@*/ addEventListener( /*@end@*/ evt, eventHandler, flag );
  }
}

ここを見るより

  • ここを見よ!

http://msdn.microsoft.com/ja-jp/library/bb250448.aspx