javascript memory leak with HTML5 Canvas getImageData in Chrome browser for mac OSX
Asked Answered
L

1

6

this problem was fixed in the new chrome version(Version 35.0.1916.114)


In chrome for mac osx, CanvasRenderingContext2D#getImageData function will make memory leaks, how can I avoid this problem, here is the test case and result, it's only happened in chrome browser, safari is OK

<!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

Lucid answered 26/5, 2013 at 9:45 Comment(1)
Also, the larger the width/height, the bigger the memory leak. Calling g.getImageData(0,0,10000, 9000); twice will crash the thread on Windows 7 SP1, with Google Chrome 27.0.1453.94.Soelch
C
4

Your problem is not with the getImageData function. It's the way the variable that holds the getImageData is assigned that create the leaks.

The problem is that delete c will fail (delete doesn't affect variable names) and the browser silently returns false.

MDN delete reference

Try with c = null instead. Try to declare the c variable outside the for loop, to avoid recreate the variable in each step of the loop.

Here is the modified code:

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
    }   
}

I tried the code exactly in the same browser and using the memory profile in developer tools I could see the memory being perfectly cleared by garbage collector.

Check the memory timeline in developer tools (Ctrl+Shift+i).

To enable the memory profile you need to start Chrome with the flag --enable-memory-info.

UPDATE:

As I’ve already said in comments, garbage collection works by reclaiming blocks of memory (objects) which are no longer reachable.

When a function returns, the object which c points to is automatically available for garbage collection, because there is nothing left that has a reference to it.

There are also misconceptions about how null works. Setting an object reference to null doesn’t “null” the object. It sets the object reference to null.

So, in this case, the memory allocated to store each getImageData information remains there until the function returns. Since the image data is a very large object, and it's larger as larger are the canvas dimensions, in huge loops (let's say 500 loops or above, that depends on the machine) will cause overflow in memory before the function returns and the garbage collector be triggered.

I recommend the following article: Writing Fast, Memory-Efficient JavaScript. It's well explained and easy to read.

SOLUTION !!!

Now we know that the garbage collector is triggered only after a function return, one solution that came into my mind is defer the function that call the getImageData by a fraction of millisecond. That way we guarantee that the function returns after each getImageData call.

I tried the code below and it work even for 10000 iterations! Spends a lot of time to finish, but it finishes and with no memory leaks!)

Try it by yourself:

<!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>
Cytologist answered 26/5, 2013 at 20:3 Comment(5)
thank you, but it seems useless, have you ever tested, and what is version of your chromeLucid
I tested in Chrome 27.0.1453.93, 27.0.1453.94 and 29.0.1518.3 canary. I don't have the memory leaked with that code. The memory grows during the while loop and is cleared by garbage collector after the function reaches its end. Seems that the real problem you are facing is not memory leak, you are overflowing the memory!!! I think that Garbage collector wont be triggered until the function reach its end. So, using enormous canvas or huge loop iterations will overflow the memory before the browser have a chance to trigger the garbage collector.Cytologist
i test it in windows xp, it's OK, maybe it is only happened in chrome for macLucid
@samsha: Check this thread in Chromium project. Did you tried put the calls like I said, or use something similar like a setInterval to do the operations and still got stuck? (I can't test on osx) If so, try to report the bug in Chromium project web site and if you get an answer post it here for us! ;)Cytologist
this problem was fixed in the new chrome version(Version 35.0.1916.114)Lucid

© 2022 - 2024 — McMap. All rights reserved.