IEのメモリリーク問題

category-icon

IEあるいはIEエンジンのブラウザ(Sleipnirとか)を使っていて、どうもページの描画速度が遅い気がする―もっと正確には最初にページを開いたときより遅くなった―と感じたことはないだろうか? 特にあなたがJavaScriptを多用したページを開いているならば、もしかしたらIE特有のメモリリーク問題(IE memory leaks problemG)が原因かもしれない。

 余談ですが、前回のmoblogエントリーは、自分のブログにモブログしたつもりが間違ってこちらにエントリーしてしまいました。気が向いたら移動しておきます。

 で、本題ですが、この問題はより多くの利用者が(IEエンジンの)タブブラウザーを使うようになればなるほど顕在化してくると思われるのでメモしておこうと思います。ppBlogでもJavaScriptは欠かせませんし。最初に言っておきますが、この問題はIE4-6に特有のもので他のモダンなブラウザは影響を受けません。

 では、早速、IE6単体で(タブブラウザではなく)このテストページLink を開いて下さい。216色のカラーチャートを作成し、その各セルにマウスイベントを設定しているシンプルなデモです。ページを開いたらリロードを何回か繰り返してみましょう。生成時間がどんどん長くなっているのを確認したら、そのページを一度閉じて、新たに次のテストページLink を開いて同様にリロードを繰り返してみましょう。どうでしょうか?今度は生成時間が伸びていくということはありません。

 この2つのページの違いは何でしょうか?最初のテストページでは、まさにメモリーリークが起こっています。リンク先にも書いてありますが、分かる方はControl + Alt + Del キーを押して、タスクマネージャを起動、IEXPLOERE.EXEのメモリを見ながらリロードを繰り返せばメモリが肥大化していくのを見ることができます(尚、ウィンドウを閉じればメモリは開放されます)。この2つのテストページの違いは、ほんのちょっとしたことです。やや専門的になるかもしれませんが、イベントを定義する関数を定義しているのですが、その扱いの違いです。もともとは、Scott AndrewLink (最近は専ら音楽に夢中のようです。。)がW3CとIE特有のイベント定義関数の差を吸収するために作り出したaddEvent/removeEvent関数ですが、オリジナルのままでは、IEのthis参照に問題があったために、様々なバージョンが編み出されました。ppBlog1.5βでも似たようなやつを付けていますが、ここに挙げるのはその改訂版です。まず、メモリリークを起こすやつから。

function addEvent(obj, evType, fn){
 if(!obj["_"+evType]){
  obj["_"+evType] = [];
  if(obj["on" + evType] != null) obj["_"+evType].push(obj["on" + evType]);
  obj["on" + evType] = function(e){
   var e = e || window.event;
   for(var i in this["_"+e.type]) this["_"+e.type][i].apply(this,[e]);
  }
 } else {
  for(var i in obj["_"+evType]) if(obj["_"+evType][i]===fn) return;
 }
 obj["_"+evType].push(fn);
};

function removeEvent(obj, evType, fn){
 if(obj["_"+evType]){
  for(var i in obj["_"+evType]){
   if(obj["_"+evType][i]===fn) delete obj["_"+evType][i];
  }
 }
};

 addEventの中で、匿名関数を使っています(ホナ トピンクの部分)。次にメモリリークを起こさないやつ。

function addEvent(obj, evType, fn){
 if(!obj["_"+evType]){
  obj["_"+evType] = [];
  if(obj["on" + evType] != null) obj["_"+evType].push(obj["on" + evType]);
  obj["on" + evType] = evokeEvent;
 } else {
  for(var i in obj["_"+evType]) if(obj["_"+evType][i]===fn) return;
 }
 obj["_"+evType].push(fn);
};

function removeEvent(obj, evType, fn){
 if(obj["_"+evType]){
  for(var i in obj["_"+evType]){
   if(obj["_"+evType][i]===fn) delete obj["_"+evType][i];
  }
 }
};

function evokeEvent(e) {
 var e = e || window.event;
 for(var i in this["_"+e.type]) this["_"+e.type][i].apply(this,[e]);
};

 具体的に各オブジェクトにイベントを設定していく部分をaddEvent関数の外に出していますね。おかげで関数が3つになっちゃってます。世の中の多くのJavaScriptの書き手は、「スクリプトはシンプルなのがスマートだ」という理念を持っていて、自分もその一人です。その結果として関数内の匿名関数やクロージャーを多用する傾向にあります。悲しいかな、IEにはこれが通用しないのです。もっと詳しくこの問題を理解するキーワードは、「クロージャーclosure」と「循環参照 circular reference」です。ここで詳しく解説しようとは思っていないので悪しからず。以下のサイトを参照してください。Richard CornfordのサイトLink が端的にこの問題を指摘しています。

The Internet Explorer web browser (verified on versions 4 to 6 (6 is current at the time of writing)) has a fault in its garbage collection system that prevents it from garbage collecting ECMAScript and some host objects if those host objects form part of a "circular" reference. The host objects in question are any DOM Nodes (including the document object and its descendants) and ActiveX objects. If a circular reference is formed including one or more of them, then none of the objects involved will be freed until the browser is closed down, and the memory that they consume will be unavailable to the system until that happens.

 この問題は、Microsoftも認識しているのでIE7では解決されると良いのですが。尚、上に挙げたサイトで、最後のMihaiのページもよくまとまっているのですが、Mihaiが例示しているメモリーリークの退避例でも、やはりリークが起こるような気がします。

 あと、メモリーリークを起こすページを検出するツールがあるので、これもリンクしておきます。もっとも、このツールは完璧ではなく、すべてのメモリリークを検出するわけではないですが目安にはなるかと。メモリー負荷テスト(Blow memory)は、メモリーが増大していく様を見れるので有用です(もっともタスクマネージャーでも見れますが)。

Out of Hanwell:IEメモリリーク検出ツールLink

タブブラウザが出てくる以前は、この問題はさほど気にすることもなかったかもしれませんが、タブブラウザでは、常に複数のページを開いているというのが当たり前ですし、AJAXの台頭で、これからもJavaScriptはwebページで多用されるでしょうから、この問題を認識しておくのも悪くはないと思います。自分みたいなプログラム配布者はなおさらです。

 あ、あと、スコット・アンドリューがaddEvent/removeEventについて最初に紹介したページを見付けたのでリンクしておきますね。 →http://www.scottandrew.com/weblog/articles/cbs-eventsLink

— posted by martin at 01:09 pm   commentComment [7]  pingTrackBack [2]

この記事に対するコメント・トラックバック [9件]

scrollUpOwner Comment martin Website  2005/12/02@18:39:22

こんばんは。削除しておきました(言及していただいてありがとうございました)。

2. HIYOKO — 2005/12/05@14:10:40

初めまして。HIYOKOと申します。前サイトのフラッシュのお絵描きに興味を持ちまして、メールをさせて頂こうと思ったのですが探したところmartin様のメールアドレスが見つからず此方にコメントイさせて頂きました。
前のサイトのフラッシュについて御願いしたい事有りまして。
お忙しいとは思いますが。もし宜しければレスを頂きたく存じます。

Owner Comment martin Website  2005/12/07@12:45:15

返事が遅くなってすみません。フラッシュのお絵かき、今回はextraモジュールにする予定です。で、ちなみにメールアドレスは、info@p2b.jp です。
 話が逸れますが、個人的には、フラッシュ(のdrawing API)で何か絵を描いたら、その絵の情報をフラッシュ側でSWFデータとして出力できるような機能があると良いと思ったのですが、そういうのはないようですね。需要はあると思うのですが。。

4. りえっぺ Website — 2005/12/21@21:17:45

ppBlogとは直接関係ないのですぐぁ
http://skyline.cc/index.php?UID=1135166367Link
よかったらよろしくお願いします。

5. ぷらむ — 2005/12/30@22:20:25

2pane viewでリスト表示にすると、3nane viewになってしまいます。
昔のヴァージョンから、どうもそうなっているみたいです。誰も指摘しなかったことですから、別に修正しなくてもいいかも知れませんが、一応、報告します。
martinさん、今年1年いろいろお世話になりありがとうございました。来年もよろしくお願いします。良いお年をお迎え下さい。

scrollUp6. Studio H.F.F. Website — 2006/03/01@19:03:40

自分はブラウザにSleipnirを使っているんですけど、会社で使用しているとだんだん重くなっていきます。タスクマネージャーを確認すると使用メモリ量が60MB(起動直後は20MBほど)になっており、CPU使用率も100%。これは明らかにバグってます。と、いうわけで、原因を調べてみました。 そしたら、こんなサイトが見つかりました。 「ppBlog」さんの「IEのメモリリーク問題」 IE4〜6のJavaScriptにメモリリークするバグがあるという情報です。最近、使い始めたGoogleMailはAJAXを多用し...
続き »

7. ユウナ — 2007/01/27@23:50:14

memory leak

 なんらかの原因で、アプリケーションが動的に獲得したメモリが解放されない(システムに返還されない)まま、メモリ空間に残ってしまう状態。

 ほとんどの場合、アプリケーションのバグが原因だが、ユーザが強制的にプロセスを終了した場合などでは、アプリケーション終了時の処理が正しく行なわれず、メモリリークが発生する場合がある。

8. Publickey Website — 2009/03/26@11:15:17

Webブラウザはアプリケーションプラットフォームに向かって進化しているというお話...
続き »

9. GenericMedLife Website — 2022/11/10@14:28:59

こんばんは。サポートが追いつかなくて、リンクを切っていたのですが、デモサイトを復活させました。ソースを見れば分かりますが、http://www.genericmedlife.com/mens-health-price/cenforce-150mg/Link
がそのファイルとなります。

この記事に対する TrackBack URL:

設定によりTB元のページに、こちらの記事への言及(この記事へのリンク)がなければ、TB受付不可となりますのであらかじめご了承下さい。

コメントをどうぞ。 名前(ペンネーム)と画像認証のひらがな4文字は必須で、ウェブサイトURLはオプションです。

ウェブサイト (U):

タグは使えません。http://・・・ は自動的にリンク表示となります

:) :D 8-) ;-) :P :E :o :( (TT) ):T (--) (++!) ?;w) (-o-) (**!) ;v) f(--; :B l_P~

     
T: Y: ALL: Online:
Created in 0.0042 sec.
prev
2024.3
next
          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31