6

此问题已在新的 chrome 版本(版本 35.0.1916.114)中修复


chrome for mac osx中, 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检查开发者工具 ( )中的内存时间线。

要启用内存配置文件,您需要使用 flag 启动 Chrome --enable-memory-info

更新:

正如我在评论中已经说过的,垃圾收集通过回收不再可访问的内存块(对象)来工作。

当函数返回时,c指向的对象会自动用于垃圾回收,因为没有任何东西可以引用它。

关于如何null运作也存在误解。将对象引用设置为null不会“为空”该对象。它将对象引用设置为 null。

因此,在这种情况下,分配用于存储每个getImageData信息的内存将保留在那里,直到函数返回。由于image data是一个非常大的对象,并且画布尺寸越大,它越大,在巨大的循环中(比如说 500 个或更多循环,这取决于机器)会在函数返回和触发垃圾收集器之前导致内存溢出.

我推荐以下文章:Writing Fast, Memory-Efficient JavaScript。它解释得很好,易于阅读。

解决方案 !!!

现在我们知道垃圾收集器仅在函数返回后触发,我想到的一个解决方案是将调用 的函数延迟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 回答