Memory leak risk in JavaScript closures
I used to work with the ex-program manager for JavaScript within Microsoft, on a non-browser EcmaScript (err.. JScr... JavaScript) project. We had some lengthy discussions about closures. In the end, the point is that they are harder to GC, not impossible. I'd have to read DC's discussion of how MS is 'wrong' that closures cause memory leaks -- for in IE's older implementations, closures were certainly problematic, because they were very hard to garbage collect with the MS implementation. I find it strange that a Yahoo guy would try to tell the MS architects that a known issue with their code was somewhere else. As much as I appreciate his work, I don't see what basis he had there.
Remember, the article you reference above refers to IE6, as IE7 was still under heavy development at the time of its writing.
As an aside -- thank god, IE6 is dead (don't make my dig up the funeral pictures). Although, don't forget its legacy... I've yet to see anyone make a credible argument that it wasn't the finest browser in the world on day 1 of its release -- the problem was that they won the browser war. And so, in what amounts to one of the larger blunders of their history -- they fired the team afterwards and it sat stagnant for nearly 5 years. The IE team was down to 4 or 5 guys doing bug fixes for years, creating a huge brain drain and falling behind the curve dramatically. By the time they rehired the team and realized where they were, they were years behind because of the added cruft of dealing with a monolothic codebase that nobody really understood anymore. That's my perspective as an internal in the company, but not directly tied to that team.
Remember too, IE never optimized for closures because there was no ProtoypeJS (heck, there was no Rails), and jQuery was barely a glimmer in Resig's head.
At the time of the writing, they were also still targeting machines with 256 megs of RAM, which also hadn't been end-of-life'd.
I thought it only fair to hand you this history lesson, after making me read your entire book of a question.
In the end, my point is that you're referencing material which is hugely dated. Yes, avoid closures in IE6, as they cause memory leaks -- but what didn't in IE6?
In the end, it is a problem that MS has addressed and continues to address. You're going to make some level of closures, that was the case, even back then.
I know that they did heavy work in this area around IE8 (as my unmentionable project used the non-at-the-time standard JavaScript engine), and that work has continued into IE9/10. StatCounter (http://gs.statcounter.com/) suggests that IE7 is down to a 1.5% market share, down from 6% a year ago, and in developing 'new' sites, IE7 becomes less and less relevant. You can also develop for NetScape 2.0, which introduced JavaScript support, but that would be only slightly less silly.
Really... Don't try to over-optimize for the sake of an engine which doesn't exist anymore.
Javascript closures and memory leak risks
You are correct, your second example would use less memory because of less closure functions. But as soon as you event isn't callable (element removed etc.) they would disappear again so it is not a "leak" as the memory isn't lost forever.
Also many plugins use the closure by setting the current state of an element in a variable instead of the element itself.
Memory leaks and closures in JavaScript - when and why?
This question asks about something similar. Basically, the idea is that if you use a closure in a callback, you should "unsubscribe" the callback when you are finished so the GC know that it can't be called again. This makes sense to me; if you have a closure just waiting around to be called, the GC will have a hard time knowing that you're finished with it. By manually removing the closure from the callback mechanism, it becomes unreferenced and available for collection.
Also, Mozilla has published a great article on finding memory leaks in Node.js code. I would assume that if you try out some of their strategies, you could find parts of your code that express leaky behavior. Best practices are nice and all, but I think it's more helpful to understand your program's needs and come up with some personalized best practices based on what you can empirically observe.
Here's a quick excerpt from the Mozilla article:
- Jimb Esser’s
node-mtrace
, which uses the GCCmtrace
utility to profile heap usage.- Dave Pacheco’s
node-heap-dump
takes a snapshot of the V8 heap and serializes the whole thing out in a huge JSON file. It includes tools to traverse and investigate the resulting snapshot in JavaScript.- Danny Coates’s
v8-profiler
andnode-inspector
provide Node bindings for the V8 profiler and a Node debugging interface using the WebKit Web Inspector.- Felix Gnass’s fork of the same that un-disables the retainers graph
- Felix Geisendörfer’s Node Memory Leak Tutorial is a short and sweet explanation of how to use the
v8-profiler
andnode-debugger
, and is presently the state-of-the-art for most Node.js memory leak debugging.- Joyent’s SmartOS platform, which furnishes an arsenal of tools at your disposal for debugging Node.js memory leaks
The answers to this question basically say that you can help the GC out by assigning null
to closure variables.
var closureVar = {};
doWork(function callback() {
var data = closureVar.usefulData;
// Do a bunch of work
closureVar = null;
});
Any variables declared inside a function will go away when the function returns, except those that are used in other closures. In this example, closureVar
has to be in memory until callback()
is called, but who knows when that will happen? Once the callback has been called, you can give a hint to the GC by setting your closure variable to null.
DISCLAIMER: As you can see from the comments below, there are some SO users who say that this information is out of date and inconsequential for Node.js. I don't have a definitive answer on that yet; I'm just posting what I've found on the web.
JavaScript Memory leak from closure lexical environment
The primary answer is that in your second code block, no direct reference to either of the closures (closure1
or someMethod
) survives the return of outer
(nothing outside outer
refers to them), and so there's nothing left that refers to the context where they were created, and that context can be cleaned up. In your second code block, though, a direct reference to someMethod
survives the return, as part of the object that you're assigning to aThing
, and so the context as a whole cannot be GC'd.
Let's follow what happens with your first block:
After the first execution of outer
, we have (ignoring a bunch of details):
+−−−−−−−−−−−−−+
aThing−−−−−>| (object #1) |
+−−−−−−−−−−−−−+
| str: ... | +−−−−−−−−−−−−−−−−−−−−+
| someMethod |−−−−>| (context #1) |
+−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−−−−+
| something: null |
| closure1: function |
+−−−−−−−−−−−−−−−−−−−−+
after the second execution:
+−−−−−−−−−−−−−+
aThing−−−−−>| (object #2) |
+−−−−−−−−−−−−−+
| str: ... | +−−−−−−−−−−−−−−−−−−−−+
| someMethod |−−−−>| (context #2) |
+−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−+
| something |−−−−>| (object #1) |
| closure1: function | +−−−−−−−−−−−−−+
+−−−−−−−−−−−−−−−−−−−−+ | str: ... | +−−−−−−−−−−−−−−−−−−−−+
| someMethod |−−−−>| (context #1) |
+−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−−−−+
| something: null |
| closure1: function |
+−−−−−−−−−−−−−−−−−−−−+
after the third execution:
+−−−−−−−−−−−−−+
aThing−−−−−>| (object #3) |
+−−−−−−−−−−−−−+
| str: ... | +−−−−−−−−−−−−−−−−−−−−+
| someMethod |−−−−>| (context #3) |
+−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−+
| something |−−−−>| (object #2) |
| closure1: function | +−−−−−−−−−−−−−+
+−−−−−−−−−−−−−−−−−−−−+ | str: ... | +−−−−−−−−−−−−−−−−−−−−+
| someMethod |−−−−>| (context #2) |
+−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−−−−+ +−−−−−−−−−−−−−+
| something |−−−−>| (object #1) |
| closure1: function | +−−−−−−−−−−−−−+
+−−−−−−−−−−−−−−−−−−−−+ | str: ... | +−−−−−−−−−−−−−−−−−−−−+
| someMethod |−−−−>| (context #1) |
+−−−−−−−−−−−−−+ +−−−−−−−−−−−−−−−−−−−−+
| something: null |
| closure1: function |
+−−−−−−−−−−−−−−−−−−−−+
You can see where this is going.
Since the second block never retains a reference to closure1
or someMethod
, neither of them keeps the context in memory.
When originally answering your question in 2015 I was slightly surprised that V8 (Chrome's JavaScript engine) didn't optimize this leak away, since only someMethod
is retained, and someMethod
doesn't actually use something
or closure1
(or eval
or new Function
or debugger
). Although in theory it has references to them via the context, static analysis would show that they can't actually be used and so could be dropped. But closure optimization is really easy to disturb, I guess something in there is disturbing it, or that the V8 team found that doing that level of analysis wasn't worth the runtime cost. I do recall seeing a tweet from one of the V8 team saying that it used to do more closure optimization than it does now (this edit is in Sep 2021) because the trade-off wasn't worth it.
Releasing closures
It was already mentioned here that :on()
invocation saves reference to callback closure inside Lua registry.
I guess this closure will be cleared from Lua registry inside __gc
metamethod of sock
object,
but sock
object will not be collected if the closure references sock
object.
To solve this problem you should avoid hard-coding a reference to sock
upvalue in the body of sendchunk()
function.
For example, exploit the fact that the first argument passed to callback function is always the socket object.
function sendfile(sock, name)
local fd = file.open(name, "r")
local function sendchunk(sck)
local data = fd:read()
if data then
sck:send(data)
else
fd:close()
sck:close()
end
end
sock:on("sent", sendchunk)
sendchunk(sock)
end
Why this is a memory leak
The following article is old, and this is not an issue anymore
The issue is described in this article:
window.onload = function() {
var obj = document.getElementById("element");
// this creates a closure over "element"
obj.onclick = function(evt) {
... logic ...
};
};
Here is a diagram describing the closure which creates a circular reference between the DOM world and the JS world.
The above pattern will leak due to closure. Here the closure's global variable obj is referring to the DOM element. In the mean time, the DOM element holds a reference to the entire closure. This generates a circular reference between the DOM and the JS worlds. That is the cause of leakage.
From the MDN web docs: A closure is the combination of a function and the lexical environment within which that function was declared.
In that case the lexical environment is the window.onload
function, which includes the obj
variable.
examples of closures in node.js that would cause memory leaks
Not a "leak" exactly, but this can be a common pitfall.
var fn = (function() {
var a = "super long string ...";
var b = "useless value";
var c = "Hello, World!";
return function() {
return c;
};
})();
This returns a function that references a scope, and every single local var in that scope will be kept, even though only one of those values are needed. This results in more memory usage than you need, especially if your function uses a small variable, but there are large values in that scope that you dont need to keep referencing.
How to fix it?
Simple option is to null out the variables you dont care about at the end of your function. The variables are still in scope, but their data would be released.
var fn = (function() {
var a = "super long string ...";
var b = "useless value";
var c = "Hello, World!";
// do stuff with a and b
a = b = null;
return function() {
return c;
};
})();
Or you could break anything that uses temp vars into it's own function so their scopes can be released. This is a better solution for a larger project.
var doSetup = function() {
var a = "super long string ...";
var b = "useless value";
// do stuff with a and b
};
var fn = (function() {
doSetup();
var c = "Hello, World!";
return function() {
return c;
};
})();
Related Topics
Convert Base64 to Image in JavaScript/Jquery
Using Settimeout on Promise Chain
How to Create an Https Server in Node.Js
Are All JavaScript Callbacks Asynchronous? If Not, How to Know Which Are
How to Make a Simple Image Upload Using JavaScript/Html
How to Get the HTML for a Dom Element in JavaScript
How to Detect Element Being Added/Removed from Dom Element
Passing Variables Through Handlebars Partial
Jquery (Almost) Equivalent of PHP's Strip_Tags()
Get Mouse Wheel Events in Jquery
Convert Column Index into Corresponding Column Letter
Dynamically Set Property of Nested Object
How Does _Proto_ Differ from Constructor.Prototype
Calling a JavaScript Function Returned from an Ajax Response
How to Use Window.Postmessage Across Domains