📝 JavaScript garbage collection doesn't work how I expected when it comes to closures. TIL! jakearchibald.com/2024/garba…

Jul 30, 2024 · 4:33 PM UTC

40
143
814
75,802
Some updates: ➡️ An IIFE is enough to trigger this leak ➡️ It's a cross-browser issue ➡️ There are other articles on this (some lower-level) ➡️ No, this isn't due to eval() jakearchibald.com/2024/garba…
5
3
52
6,391
Sort replies: Relevant Recent Liked
Replying to @jaffathecake
I guess for this to work perfectly one would need to do the impossible, because whether an arbitrary function will read or not a value from its closure could depend on an arbitrary computation, I guess it makes sense to just not try to be clever here 🤔 Interesting finding.
2
1,619
That isn't possible. Some of the examples in the post show this is happening.
1
1,380
Replying to @jaffathecake
So... basically it's all or nothing. Scope with all referenced variables is retained and shared by all callbacks even though particular function doesn't use it. And it is GC'ed only after references to all functions from that scope are lost.
2
1
20
1,528
Replying to @jaffathecake
Nice explanation, specifically about something in scope being callable. Did you know about these? github.com/naugtur/js-traini… github.com/naugtur/js-traini…
1
4
649
I didn't. Is there an explanation somewhere of why this happens? Has an issue been filed?
1
1
504
Replying to @jaffathecake
Thanks for sharing. That also breaks my intuition. Seems like an avoidable oversight in V8. Do you see it in other engines?
1
3
1,169
I think it's the same in other engines, but it's worth checking
1
1
1,056
Replying to @jaffathecake
Isn't this expected tho? Whenever there is a closure, the variables they might need from parent function's scope is retained. Returned callback in this case needs "id" variable, which is returned from a timeout that references to the large array buffer.
1
2
1,128
Right, but in this case you can categorically say the buffer cannot be referenced
5
991
Replying to @jaffathecake
The setTimeout call cannot be statically analyzed, so it needs to stay in case setTimeout will call that function any number of times. It is possible that someone could override the setTimeout global too, and if they did, it may call the callback any time.
2
1
743
That's solved at runtime. setTimeout releases its reference to the function when it no longer wants to call it. This isn't browser magic, userland code can do exactly the same thing.
1
3
650
Replying to @jaffathecake
Would it be possible to detect this risk of leakage with static analysis? I wonder if there is a eslint rule somewhere for that?
1
151
It feels more like something that needs to happen at runtime, but I could be wrong.
1
153
Replying to @jaffathecake
What tooling do you use to track collection? That’s always been a weird thing for me to measure
1
1
100
Using heap allocation in the Chrome memory dev tools
2
110
Replying to @jaffathecake
youtu.be/6Ixyltr8_R0?si=Rxk7… looks like a good explanation of why
1
14
I don't think so. I'm pretty sure the userData example at the end would get GCd because it isn't referenced by inner functions.
12
Replying to @jaffathecake
is there a way to capture a variable by value so we don’t need to capture the closure context ?
1
95
What does "by value" mean for an object?
1
98
Replying to @jaffathecake
Are you demonstrating that even though the timeout has ran, the buffer isn't collected? And the only way to collect it is to explicitly clearTimeout()?
1
522
No. See the final example of the article.
1
487