11

Javascript ライブラリのテスト中に、IE10 (v10.0.9200.16519 - Windows 8 64 ビット) の Javascript 実装で重大なメモリ リークが見つかったと思いますsetInterval

簡単なテスト ケースでは、後で実行するための引数として渡される関数のクロージャで変数がキャプチャされた場合、ガベージ コレクションの対象になることはないようです。つまり、ブラウザは依然として関数への参照を保持しているように見えます。少なくとも閉鎖変数。

テストケースは関数を 1 回だけ実行しsetInterval、インターバル タイマーをクリアします。つまり、しばらくすると、コードが実行されなくなり、変数にアクセスできなくなります (実行するメソッドを除いて、このコードにはグローバルが導入されていないことがわかります)。でonload)、それにもかかわらず、プロセスは(反復回数に応じて)0.5ギガバイトのメモリを消費します。

興味深いことに、setTimeout代わりにメソッドを使用すると、これは発生しません (また、問題はIE9 および Chrome の現在のバージョン、FF には存在しないようです)。

問題は、この fiddleで見ることができます。

Windows 8 の IE10 の新しいインスタンスで実行し、タスク マネージャーを開いてメモリ使用量を監視します。すぐに 350 メガバイトまで大きくなり、スクリプトが実行された後もそこにとどまります。

これは、問題のあるコード部分の重要な部分です。

// the function that when called multiple times will cause the leak in IE10
var eatMemory = function() {
    var a = null; // the captured closure variable
    var intervalId = setInterval(function() {
       a = createBigArray(); // call a method that allocates a lot of memory
       clearInterval(intervalId); // stop the interval timer
    }, 100);
}

(この特定のコードを簡単に修正できることはわかっています。しかし、それは問題ではありません。これは、問題を再現するために私たちが思いついた最も小さなコードにすぎません。実際のコードは実際thisにクロージャーでキャプチャされ、そのオブジェクトはガベージ コレクションは行われません。)

私たちのコードにバグがありますか、それともsetIntervalクロージャ変数が大きなオブジェクトへの参照を保持する場合に、メモリ リークを引き起こさずに、また「再帰的」setTimeout呼び出しに戻らずに使用する方法はありますか?

MSDNにも質問を投稿しました

更新:この問題は Windows 7 の IE10 でも発生しますが、IE9 標準モードに切り替えると発生しません。これを MS Connect に送信しました。進捗状況を報告します。

更新: Microsoftはこの問題を受け入れ、 IE11 (プレビュー バージョン) で修正されると報告しました - 私自身はまだ確認していません (誰か?)

更新: IE 11 が正式にリリースされましたが、私のシステム (Win 8.1 Pro 64 ビット) でそのバージョンの問題を再現できなくなりました。

4

1 に答える 1

7

完全を期すために、考えられる回避策をここに追加します。

私がすでに書いたように (そしてコメンテーターが示唆したように)、これは にフォールバックすることで回避できます (修正されません) setTimeout。ID の簿記を行う必要があるため、これは簡単なことではありません。このフィドルからテストしてフォークできる、私の提案する修正を次に示します。

var registerSetIntervalFix = function(){
    var _setTimeout = window.setTimeout;
    var _clearTimeout = window.clearTimeout;
    window.setInterval = function(fn, interval){
        var recurse = function(){
            var newId = _setTimeout(recurse, interval);
            window.setInterval.mapping[returnValue] = newId;
            fn();
        }
        var id = _setTimeout(recurse, interval);
        var returnValue = id;
        while (window.setInterval.mapping[returnValue]){
            returnValue++;
        }
        window.setInterval.mapping[returnValue] = id;
        return returnValue;
    }
    window.setInterval.mapping = {};
    window.clearInterval = function(id){
        var realId = window.setInterval.mapping[id];
        _clearTimeout(realId);
        delete window.setInterval.mapping[id];
    }
}

アイデアは、setTimeout繰り返し呼び出しをシミュレートするために再帰的に呼び出すことsetIntervalです。この実装では、変化するids の簿記を実行する必要があるため、多少のオーバーヘッドがあります。そのため、必要でない限り、この修正を適用することはお勧めしません。

残念ながら、「機能」検出アルゴリズム (「バグ」検出アルゴリズムのようなもの) を考え出すことができないため、古き良きブラウザー検出に戻す必要があると思います。また、私の実装は最初の引数として文字列を処理できず、追加の引数を内部関数に渡しません。最後に、このメソッドを 2 回呼び出すのは安全ではないため、自己責任で使用してください (自由に改善してください)。

(注: 私たちのライブラリについては、今後は使用を中止しsetInterval、代わりにライブラリに依存するコード内のいくつかの部分をsetTimeout直接使用するように書き直します。)

于 2013-04-10T07:22:42.480 に答える