How does setTimeout() create a memory leak in this code?
This is definitely a memory leak. However, the memory consumption is so small and cannot be measured. I did some small changes to the source code.
- I put the entire code inside a loop to create the same scenario 100,000 times
- I increased the timer interval to about 16 minutes. This prevents browser from crashing
Here is the code:
for (var i = 0; i < 100000; i++) {
var buggyObject = {
callAgain: function() {
var ref = this;
var val = setTimeout(function() {
ref.callAgain();
}, 1000000); //16m
}
}
buggyObject.callAgain();
buggyObject = null;
}
My experiment:
I ran the code in Chrome Version 34.0.1847.116 m and captured memory changes using Developer Tools\Timeline.
As we can see in the picture, around 32 MB of memory has been consumed for running this code and after a while it's been decreased to about 30 MB and stayed unchanged (see #1).
After several garbage collecting attempts by Chrome (see #2) and one manual forced garbage collection by me (see #3, #4), the memory consumption remains unchanged.
There is no more buggyObject
and there is nothing we can do to release the memory. The only possible way is to close the browser.
What causes this?
The main reason for this behavior is the timer. Timer callback and its tied object, buggyObject will not be released until the timeout happens. In our case timer resets itself and runs forever and therefore its memory space will never be collected even if there is no reference to the original object.
There is another question that describes how setTimeout() looks like it has memory leaks, but in reality does not.
However, I think what the author is trying to say is that since buggyObject
creates a setTimeout which calls itself, even if you change buggyObject to equal null
(saying you are done with the object and it can be cleaned up), the object won't be garbage collected because there is still a reference to it in the setTimeout(). This is technically a memory leak because there is no longer any direct reference to the setTimeout function so that you could clear it later (kind of a zombie timeout if you will).