5 ms·
Uncollectable reference cycles are shockingly easy to make in JS, especially with React. A classic example: function closure() { var smallObject =
by Jasper_ 2y ago
Uncollectable reference cycles are shockingly easy to make in JS, especially with React. A classic example:
function closure() {
var smallObject = 3;
var largeObject = Array(1000000);
function longLived() { return smallObject; }
function shortLived() { return largeObject; }
shortLived(); return longLived;
}
Will keep largeObject alive.
- kevingadd 2y agoJS runtimes are allowed to optimize this out, IIRC, and will often do so.
- cobbal 2y agoIs this a property of JavaScript or the engine running it? This feels like something a sufficiently-smart™ closure implementation should be able to prevent.
- Jasper_ 2y agoThis is an artifact of V8's GC. Effectively, largeObject and smallObject are tracked together, as a unit. Splitting it out into two separate records increases average memory usage. They keep saying they want to fix it eventually, but it's been this way for 10+ years at this point. You really do have to know the quirks of what you're targeting.
- Dylan16807 2y ago> sufficiently-smart™ Well, the reference lua implementation handles this case fine, and it's a solid but not at all smart™ codebase.
- AgentME 2y agoThis isn't an uncollectable reference cycle. It's true that with this code in most/all JS engines, if there's a reference to the function `longLived` then `largeObject` will be kept in memory, but reference cycles are collectable in standard garbage collection systems. Both of the values will be garbage-collectable once no outside references to `longLived` still exist. Pure reference counting systems (Rust Rc, C++ shared_ptr, etc) are the kind of system that fail to automatically handle cycles. You could test this with your code by setting a FinalizationRegistry to log when they're both finalized, unset any outside reference to `longLived`, and then do something to force a GC (like run Node with --expose-gc and call `global.gc()`, or just allocate a big Uint8Array).
- deleted 2y ago[deleted]
- moonchild 2y agoGeez! Didn't henry baker warn against implementing closures this way decades ago? This is an implementation bug—the code is fine.