Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Leaving a handler on img.onload is a memory leak?

Is it correct that this code causes a memory leak in the browser?

/**
 * @param {Canvas2DRenderingContext} ctx
 * @param {string} url
 */
function loadImageDrawIntoCanvas(ctx, x, y, url) {
  var img = new Image();
  img.onload = function() {
    ctx.drawImage(img, x, y);
  }
  img.src = url;
};

My understanding is that because img is a DOM element and because I'm attaching JavaScript to it with img.onload the browser will never garbage collect this. To correct that I'd need to clear img.onload as in

/**
 * @param {Canvas2DRenderingContext} ctx
 * @param {string} url
 */
function loadImageDrawIntoCanvas(ctx, x, y, url) {
  var img = new Image();
  img.onload = function() {
    ctx.drawImage(img, x, y);
    img.onload = null;          // detach the javascript from the Image
    img = null;                 // needed also so the closure doesn't keep
                                // a reference to the Image?
  }
  img.src = url;
};
like image 763
gman Avatar asked Oct 19 '22 10:10

gman


1 Answers

It should not be a leak, as long as it's implemented correctly by the browser.

Old versions of Internet Explorer (7 and earler) had a GC that couldn't deal with circular references between JS and DOM nodes. There are a lot of guides out there that suggest clearing event listeners before removing DOM nodes because of this, and jQuery does this automatically. (NB: Other browsers might have had fautly GCs at some point, but old IE is the famous one.)

The interesting part here is that the GC needs to know if onload will be fired again in the future or not.

I just tried rendering 275 MB of images to a canvas using code similar to what you posted, and Chrome does not leak. (By comparison, if I store the images in an array outside of the loop, then 275 MB are retained.) Firefox might leak [some?], but it's hard to tell because its memory overhead is much higher than Chrome's.

Why?

  • On the javascript-side, onload and loadImageDrawIntoCanvas both complete execution and no remaining references to the img remain.
  • On the browser implementation-side, they've done something functionally equivalent to incrementing the reference count on img when you call img.src=, and decrementing it once onload is fired. Chrome has two tests for these sorts of leaks (1, 2).
like image 59
ZachB Avatar answered Oct 21 '22 02:10

ZachB