Ajaxにおけるメモリリークの注意点 - suVeneのアレ

Ajaxにおけるメモリリークの注意点

[][][]
はてブの「ブックマークの確認」ページは、IEでメモリリークする!

IE のメモリリーク調べる為の「Drip」ってツールが ここにあって、
このツールは単純にリークしそうなコードチェックしたり、
オートリロードして、外部から参照したメモリ使用量を記録してくれるだけなんだが
はてブの追加ページで確認するとこんな感じになる。
(オートリロードなんで負荷高くなるから、悪用したり、やりすぎたりしないようにw)

20060306_01.png

タスクマネージャー等の、外部から参照したメモリが増えているからといっても
OSがアプリケーション用にキャッシュしているメモリが増加しているだけの
可能性があるから、一概に鵜呑みはできないんだが
平均して1回のリロードに 1M 近く増えていくとかおかしい。
(MicroSoft もタスクマネージャーで確認しろっつってるし)

IE単独だと、閉じた時点でメモリ解放されるんだが
タブブラウザだと、タブブラウザ自体を閉じないと解放されないので
しょっちゅうブクマしながらもブラウザ閉じないから、どんどん重くなっていく。

つー訳で、Ajax が話題になってて Tips や Sample ばかり取り上げられているが、
そういった例は簡略化のため、エラー処理やクローズ処理を省いている事が多いから、
以下に Memory Leak する例をあげようと思う。
(サンプルをコピペするのはかまわんし、そもそもIEのバグだから知らんがな!
って思うだろうが、一応ね)

1. 単純な循環参照
オブジェクトがDOMノードを参照し、そのノードがオブジェクトを参照する。

element1.nextElement = element2;
element2.prevElement = element1;

ツリーの親子関係覚えとく為に、子ノードに親ノードを入れとくとかもダメやね。
解決策は、 unload するときに、 null つっこんどけって話らしい。

2. クロージャの循環参照
こっちが特にありがち。

function doHoge(element) {
element.onClick = function() {
// do domething
}
}

引数にDOMノードを取り、その関数の中でエレメントのプロパティまたは
イベントにクロージャを突っ込む。クロージャは doHoge の引数 element を参照し、
element がクロージャを参照するという循環参照。
これの解決策は後で述べる。

3. 動的なDOMノードの作成後に親ノードに紐付ける順番

var parentDiv = document.createElement("<div onClick='foo()'>");
var childDiv = document.createElement("<div onClick='foo()'>");
parentDiv.appendChild(childDiv);

文書ツリーに属さないまま、イベント付きのエレメントを紐付けてはダメらしい。
解決策は childDiv を parentDiv に紐付ける前に

document.getElementById('hoge').appendChild(childDiv);

と、先に文書ツリーに紐付けろとの事。

ほとんど、ここに書いてある事を、書いてるだけだなこれだとw

んでまぁ、問題なのは、ってか一番避けにくいのが 2. な訳です。
なんでかつーと、エレメントのイベントに登録したオブジェクトの中に
クロージャを使って処理をする事はテクニック的に多々あるわけであり、
それの解決策としては、window の unload 時に detachEvent で
登録したイベントを解除しなくてはいけない。

だから、自力でやろうとすると無名のクロージャをイベントハンドラに
直接紐付けると、解除する時にクロージャの参照が取れないからダメで、
一時的な変数に入れておかなければならないってことになる。

まぁそんな事は個々にやるのも面倒なので汎用的に処理したい人は
prototype.js の Event.observe() を使えばよい。
prototype.js には(1.4.0見て話てる)

/* prevent memory leaks in IE */
Event.observe(window, 'unload', Event.unloadCache, false);

ってコードが入ってて、これは observe で登録した関数オブジェクトの配列を
全て解放してくれている。

prototype.js 使わずに、自力でライブラリ作って公開してる人とかは
unload 時に後片付けしてくれって事だな。

はてなのブックマーク追加ページも 2. によるリークである。
最近のはてなは、 prototype.js でイベント登録しているものが多いが
はてブのページ追加は独自に処理していて、tag_suggest.js で
エレメントを引数に取る関数から、そのエレメントに対してクロージャを設定し、
unload で処理していない。

つー訳で、ブラウザ重くなるの嫌なのではてなアイデア
ポイント入れてもらえれば嬉しいわけです!

* 追記
はてブから着てくれる人が多いので追記。
アイデアについては、対応してくれました。
1000 hatena 『prototype.js を利用するように変更しました。』 (2006-03-13 15:05:05)

* 関連記事
[はてなアイデア]IE6SP1でメモリリークの問題があるのでtag_suggest.js などで登録しているイベントをUnload時に detach するか、prototype.js などの Event.ovserve() 使って欲しい。
Ajaxするときメモリリークに気をつけろ

スポンサーリンク
スポンサーリンク

コメント

  1. @IT編集 より:

    はじめまして。@IT編集の富嶋と申します。
    次回19日公開予定の「AjaxうきうきWatch」という連載でこのページを紹介させていただきたいと考えています。ご連絡先が見つからなかったため、コメントでお伺いさせていただきます。よろしくお願いします。

  2. suVene より:

    ご連絡ありがとうございます。
    宜しくお願いします。

  3. Silky より:

    なるほど参考になります。
    ひとつ腑に落ちないのですが、「クロージャ」の意味を取り違えている気配があって、そこだけ内容を理解しずらい感がします。

  4. suVene より:

    「クロージャ」は、局所化(ブロック化)されたコードで、宣言された時の変数を参照でき、かつブロック内の変数を局所化したものを呼ぶんだと思ってました。故に、例に挙げている無名関数も「クロージャ」と呼べると思うのですが違うのでしょうか?

    // do domething 内で、doHoge の引数である、element が参照できるので、クロージャと呼べると思うのですが、勘違いしているのでしょうか。(無名関数 = クロージャ とは思っておりません)

    お暇があれば、勘違いしていると感じた点を教えていただけますでしょうか。

  5. 色々あるんだなぁ / Ajaxにおけるメモリリークの注意点 http://t.co/i1VBXqFg

  6. SAI より:

    @hysdijr なんかそれっぽい記事あったから一応送っとく。 http://t.co/fReGrE5u http://t.co/g9ZLrhJo http://t.co/18hrCw6x

  7. 024t910_fav より:

    >> Ajaxにおけるメモリリークの注意点 – suVeneのアレ http://t.co/qTVQqzY5

コメントをどうぞ

メールアドレスが公開されることはありません。