8

I need some help here to undestand how this works (or does not, for that matter).

  1. In a web page, I create a click event listener for a node.
  2. Within the listener, I create an instance of some random class, which sets the node as property within itself. So, if var classInstance is the instance, I can access the node as something like classInstance.rootNode.
  3. When the listener fires, I setup an ajax request, keep classInstance in closure and pass along the ajax response to classInstance and use it to perhaps modify the rootNode's style or content or whatever.

My question is, once I'm done with classInstance, assuming nothing else references it and by itself, it holds nothing else in its own closure, will the garbage collector dispose of it? If not, how do I mark it for disposal?

Jibi Abraham
  • 4,636
  • 2
  • 31
  • 60
  • 2
    Show us that event listener's code. But it sounds like it should be collected once the ajax returns - after that no code-to-execute references it any more. – Bergi Sep 27 '12 at 11:43
  • @Bergi: Nips, this is what I was trying to say... I typed up something like half of _the DaVinci Code_ what you manage to summarize in 1 comment... :) – Elias Van Ootegem Sep 27 '12 at 11:56
  • @Bergi - Ahh, the code is based on javelin, too much to explain if I post samples here. But I get the gist of what you're trying to say. Thanks – Jibi Abraham Sep 28 '12 at 06:54

2 Answers2

8

In response to @Beetroot-Beetroot's doubts (which, admittedly, I have, too), I did some more digging. I set up this fiddle, and used the chrome dev-tools' timeline and this article as a guideline. In the fiddle, two almost identical handlers create a closure with 2 date objects. The first only references a, the second references both a and b. Though in both cases only a can ever really be exposed (hard-coded values), the first closure uses significantly less memory. Whether or not this is down to JIC (just in time compilation) or V8's JS optimization wizardry, I can't say for sure. But from what I've read, I'd say it's V8's GC that deallocates b when the tst function returns, which it can't in the second case (bar references b when tst2 returns). I get the feeling that this isn't that outlandish, and I wouldn't at all be surprised to find out that FF and even IE would work similarly.
Just added this, perhaps irrelevant, update for completeness' sake and because I feel as though a link to google's documentation of the dev-tools is an added value of sorts.


It sort of depends, as a simple rule of thumb: as long as you can't reference the classInstance variable anymore, it should be GC'ed, regardless of its own circular references. I've tested quite a lot of constructions, similar to the one you describe here. Perhaps it's worth a look
I've found that closures and mem-leaks aren't that common or easy to get by (at least, not anymore).

But as the accepted answer says: it's nigh impossible to know when what code will leak.
Reading over your question again, I'd say: no, you're not going to leak memory: the classInstance variable wasn't created in the global scope, but it's being passed to various functions (and therefore various scopes). These scopes disintegrate each time the function returns. classInstance won't be GC'ed if it's been passed to another function/scope. But as soon as the last function that references classInstance returns, the object is marked for GC. Sure it might be a circular reference, but it's a reference that cannot be accessed from anywhere but its own scope.
You can't really call that a closure, either: closures happen when there is some form of exposure to the outer scope, which is not happening in your example.

I'm rubbish at explaining stuff like this, but just to recap:

var foo = (function()
{
    var a, b, c, d;
    return function()
    {
        return a;
    }
})();

The GC will deallocate the mem b,c and d reference: they've gone out of scope, there's no way to access them...

var foo = (function()
{
    var a, b, c, d;
    return function()
    {
        a.getB = function()
        {
            return b;
        }
        a.getSelf = function()
        {
            return a;//or return this;
        }
        return a;
    }
})();
//some code
foo = new Date();//

In this case, b won't get GC'ed either, for obvious reasons. foo exposes a and b, where a is an object that contains a circular reference. Though as soon as foo = new Date(), foo loses any reference to a. Sure, a still references itself, but a is no longer exposed: it can reference whatever it bloody well likes. Most browsers won't care and will GC a and b. In fact, I've checked and Chrome, FF, and IE8 all GC the code above perfectly... no worries, then.

Community
  • 1
  • 1
Elias Van Ootegem
  • 74,482
  • 9
  • 111
  • 149
  • > The GC will deallocate the mem b,c and d reference: I used to think this was the way GC behaved but later came to understand that every member in the outer environment was preserved whether or not it was used by the inner. I've been looking for a definitive reference for this GC behaviour in javascript. Do you know of one? – Beetroot-Beetroot Oct 07 '12 at 00:37
  • @Beetroot-Beetroot: I don't think there is any _definitive_ reference available, the way a GC works depends on the various engines. But since V8 is open source, you might do some digging. I just based this presumption on the GC being of the flag-swipe kind. Assuming it flags `d` because it's no longer being referenced and therefore can be deleted. Of course,behind the scenes JS works with a caller object, so it could be that only the entire scope gets deallocated in the end... so honestly: I don't think anyone knows for sure – Elias Van Ootegem Oct 07 '12 at 12:08
  • Thanks Elias, your thoughts and uncertainties are very similar to my own. I will indeed do some research on V8 - good suggestion. Thanks again. – Beetroot-Beetroot Oct 07 '12 at 20:51
  • @Beetroot-Beetroot: I've done some checking, using [this fiddle](http://jsfiddle.net/TGwQa/show/), the [chrome dev-tools' timeline](https://developers.google.com/chrome-developer-tools/docs/timeline) and [this article](http://zetafleet.com/blog/google-chromes-heap-profiler-and-memory-timeline) I'm quite convinced now that V8 does collect each individual reference as soon as it can. In both closures, 2 date objects are created, but The first closure takes up considerably less memory then the second. The reason might be either JIC or V8-optimization, either way, my initial _"theory"_ stands – Elias Van Ootegem Oct 08 '12 at 08:19
  • Elias, interesting. I ran your fiddle in Opera, updated with a x10000 loop to exaggerate the effect, and the memory consumed was (with some small experimental error) the same for tst() and tst2(). I found I had to "Update" the fiddle between tests to get back to the same baseline. "Run" between tests failed to recover the memory, and gave inconclusive results. – Beetroot-Beetroot Oct 08 '12 at 11:36
  • @Beetroot-Beetroot: Right, back to where we started, then: It depends on the various implementations, I guess. I would like to know more about this stuff. [I find in genuinely interesting](http://stackoverflow.com/questions/11186750/memory-leak-risk-in-javascript-closures). And since you bothered testing this, I take it you're interested in this, too. So I'm just adding a link to a question of mine, perhaps you might be able to shed some light on the matter there, or find out something new (you never know... :-P) – Elias Van Ootegem Oct 08 '12 at 11:46
  • 1
    Elias - yes I'm very interested. Maybe together we can develop a "Van-Ootegem-Beetroot" benchmark! No time right now but I will be back here later today. – Beetroot-Beetroot Oct 08 '12 at 12:02
  • Just let me know when and where, I'll get down and dirty with V8, and succeed where the army of highly skilled developers at google failed :). Joking aside, it's nice to know that I'm not the only one who finds memory management interesting stuff. – Elias Van Ootegem Oct 08 '12 at 12:11
2

I'm not an expert on this issue, but I'm pretty sure the GC will not dispose of it. In fact, it probably never will, because you've created a circular reference between the event listener and the DOM node. To allow it to be garbage-collected, you should set one or both of those references (the event listener and/or rootNode) to undefined or null.

Still, I'd only worry about this if you're creating many of these classInstances or if it can be created several times over the page's lifetime. Otherwise it's an unnecessary optimization.

jonvuri
  • 5,738
  • 3
  • 24
  • 32
  • 1
    You sound like an IE garbage collector :-) Of course circular references will also be collected, even if they include host elements – Bergi Sep 27 '12 at 11:40