谷歌浏览器在Mac OSX中使用HTML5 Canvas的getImageData功能会导致JavaScript内存泄漏。

6

这个问题在新版谷歌浏览器(版本号35.0.1916.114)中已经得到解决。


Mac OSX 上的 Chrome 浏览器中,使用 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>

enter image description here


此外,宽度/高度越大,内存泄漏就越严重。在Windows 7 SP1上使用Google Chrome“27.0.1453.94”调用g.getImageData(0,0,10000, 9000);两次将导致线程崩溃。 - Qantas 94 Heavy
1个回答

4
您的问题不在于getImageData函数,而在于保存getImageData的变量赋值方式可能会导致泄露

问题是delete c将失败(delete不影响变量名),并且浏览器会静默返回false。

MDN delete reference

尝试使用c = null代替。尝试在for循环之外声明c变量,以避免在每个循环步骤中重新创建变量。

这是修改后的代码:

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)。

要启用内存分析,您需要使用标志--enable-memory-info启动Chrome。

更新:

正如我在评论中所说,垃圾回收是通过回收不再可达的内存块(对象)来工作的。

当函数返回时,c指向的对象自动可供垃圾回收,因为没有任何剩余的引用它。

还存在有关null的误解。将对象引用设置为null并不能“null”对象。它将对象引用设置为null。

因此,在这种情况下,分配用于存储每个getImageData信息的内存仍然保留,直到函数返回。由于image data是一个非常大的对象,并且随着画布尺寸越来越大而变得更大,在巨大的循环中(比如500次或以上,这取决于机器),将在函数返回之前导致内存溢出并触发垃圾回收器。

我推荐阅读以下文章:编写快速、内存高效的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>

谢谢,但似乎没用,你测试过吗?你的Chrome版本是多少? - sam sha
2
我在 Chrome 27.0.1453.93、27.0.1453.94 和 29.0.1518.3 canary 上进行了测试。我用那段代码没有出现内存泄漏的情况。内存会在 while 循环期间增长,并在函数结束后由垃圾收集器清除。看起来你面临的真正问题并不是内存泄漏,而是内存溢出!我认为垃圾回收器只有在函数执行完毕后才会被触发。因此,在浏览器有机会触发垃圾回收器之前,使用巨大的画布或大量的循环迭代会导致内存溢出。 - Gustavo Carvalho
我在Windows XP上测试过了,没问题,可能只是在Mac的Chrome浏览器中出现了这个问题。 - sam sha
@samsha:在Chromium项目中查看此线程。你是否尝试按照我说的方式调用,或者使用类似setInterval的方法执行操作,但仍然卡住了?(我无法在OSX上测试)如果是这样,请尝试在Chromium项目网站上报告错误,如果得到答案,请在此处发布!;) - Gustavo Carvalho
2
这个问题已经在新的Chrome版本(版本35.0.1916.114)中得到了修复。 - sam sha

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接