6

この問題は、新しい chrome バージョン (バージョン 35.0.1916.114) で修正されました。


Mac osxchromeでは、CanvasRenderingContext2D#getImageData 関数でメモリ リークが発生します。この問題を回避するにはどうすればよいですか。テスト ケースと結果は次のとおりです。これは chrome ブラウザでのみ発生し、safari は問題ありません。

<!DOCTYPE html>
<html>
<head>
    <title>CanvasRenderingContext2D#getImageData bug in chrome</title>
    <script type="text/javascript">

    var g;
    function init(){
        g = document.getElementById('canvas').getContext('2d');
        g.fillStyle = "blue";
        g.fillRect(10, 10, 100, 100);
        g.fillStyle = "green";
        g.fillRect(60, 60, 100, 100);
    }

    function getImageData(){
        var i = 0;
        while(i++ < 100){
        var c = g.getImageData(0,0,1000, 1000);
        delete c;
        }
    }

    function toDataURL(){
        var i = 0;
        while(i++ < 100){
        var c = g.canvas.toDataURL();
        delete c;
        }
    }
    </script>
</head>
<body onload="init()">
<button onclick="getImageData()">call getImageData 100 times - then memory will grow, can't GC</button>
<button onclick="toDataURL()">call toDataURL 100 times - it is OK</button><br>
<canvas id='canvas' width='600px' height='500px'/>
</body>
</html>

ここに画像の説明を入力

4

1 に答える 1

4

あなたの問題はgetImageData機能にありません。を保持する変数getImageDataが割り当てられているのは、リークを作成する方法です。

問題は、それdelete cが失敗し (削除は変数名に影響を与えません)、ブラウザーが何も言わずに false を返すことです。

MDN 削除リファレンス

c = null代わりに試してみてください。ループの各ステップで変数を再作成しないように、ループのc外で変数を宣言するようにしてください。for

変更されたコードは次のとおりです。

function getImageData(){
    var i = 0;
    var c;
    while(i++ < 100){
        c = g.getImageData(0,0,1000, 1000);
        // c = null; // <= check UPDATE to see why this doesn't work as expected
    }   
}

function toDataURL(){
    var i = 0;
    var c;
    while(i++ < 100){
        c = g.canvas.toDataURL();
        // c = null; // <= check UPDATE to see why this doesn't work as expected
    }   
}

同じブラウザでコードをまったく試してみたところ、開発者ツールでメモリ プロファイルを使用すると、ガベージ コレクタによってメモリが完全にクリアされていることがわかりました。

開発者ツール ( ) でメモリ タイムラインを確認しますCtrl+Shift+i

メモリ プロファイルを有効にするには、フラグを付けて Chrome を起動する必要があります--enable-memory-info

アップデート:

コメントで既に述べたように、ガベージ コレクションは、到達できなくなったメモリ ブロック (オブジェクト) を再利用することによって機能します。

関数が戻ると、そのオブジェクトcへの参照を持つものが何も残っていないため、そのオブジェクトは自動的にガベージ コレクションに使用できるようになります。

働き方についても誤解がありますnull。オブジェクト参照を に設定しても、オブジェクトnullは「null」になりません。オブジェクト参照を null に設定します。

したがって、この場合、各getImageData情報を格納するために割り当てられたメモリは、関数が戻るまでそこに残ります。はimage data非常に大きなオブジェクトであり、キャンバスのサイズが大きいほど大きくなるため、巨大なループ (500 ループ以上、マシンによって異なります) では、関数が返される前にメモリでオーバーフローが発生し、ガベージ コレクターがトリガーされます。 .

次の記事をお勧めします:高速でメモリ効率の高い JavaScript の記述。よく説明されていて読みやすいです。

解決 !!!

これで、ガベージ コレクターが関数の戻り後にのみトリガーされることがわかりました。私の頭に浮かんだ 1 つの解決策は、関数を呼び出す関数をミリ秒単位で遅らせることです。getImageDataそうすれば、各 getImageData 呼び出しの後に関数が戻ることが保証されます。

以下のコードを試してみましたが、10000回の反復でも機能します! 完了するまでに多くの時間がかかりますが、メモリ リークなしで完了します!)

自分で試してみてください:

<!DOCTYPE html>
<html>
<head>
<title>CanvasRenderingContext2D#getImageData bug fixed</title>
<script type="text/javascript">

var g;
function init(){
    g = document.getElementById('canvas').getContext('2d');
    g.fillStyle = "blue";
    g.fillRect(10, 10, 100, 100);
    g.fillStyle = "green";
    g.fillRect(60, 60, 100, 100);
}

function getImageData(){        
    var c = g.getImageData(0,0,1000, 1000);     
}

var total = 0;
var iterations = 100;

function test(){
    var i = 0;

    while(i++ < iterations){
        setTimeout(function(){          
            getImageData();
            total++;
            //console.log(total);
            if(total == iterations){
                alert("" + total+" getImageData functions were completed!!!")
            }
        }, 0.01); // defer
    }
    alert("" + (i-1) + " iterations completed. Wait for the return of all getImageData");
}
</script>
</head>
<body onload="init()">
<button onclick="test()">call getImageData several times</button><br>
<canvas id='canvas' width='600px' height='500px'/>
</body>
</html>
于 2013-05-26T20:03:22.497 に答える