2

I have this following code snippet:


function outer() {
  let a

  return function inner() {
    
     a = new Uint8Array(100000)
     const b = new Uint16Array(100000)
  };
};
const fn = outer(); 
fn()

Took one heap snapshot, ran it in a about:blank page in chrome 103.0.5060.114, and took a second snapshot. Comparing the two snapshots, I found that in the second snapshot, there is one more Uint8Array. That means a is retained in memory, so it leaked. But I can't find any Uint16Array so b didn't leak. enter image description here enter image description here

But I couldn't figure out why that Uint8Array is leaking because it doesn't seem like I can still reference it outside the outer function. So According to the garbage collection algorithm, it should have been collected by now.

Haven't tested this in other browsers or in Node.

11
  • 2
    a is retained because it is used by inner. inner==fn. If you delete fn I assume a will be gone. Commented Jul 5, 2022 at 23:07
  • @ITgoldman well b is also used by inner but it didn't get leaked. Plus I didn't return either so they are both unreachable from outside and thus should be all GC'ed. Commented Jul 5, 2022 at 23:08
  • 1
    b is irrelevant because it is just a local variable, gone when the function ends. however a exists while the function exists. He is bound to it or the other way. Commented Jul 5, 2022 at 23:11
  • well, no, once a finishes executing, there is no way to reach to a anymore. it should have been GC'ed Commented Jul 5, 2022 at 23:17
  • 1
    @Joji The variable a is still referenced (closed over) by fn/inner, which is reachable (alive) itself, so it won't get garbage-collected. That there exists no code that can retrieve (read from) a, or - in more complicated cases - that all code that does retrieve a is not reachable from the current state of the program and won't be executed, is not taken into account by the garbage collector. This would require advanced program analysis techniques, and is not generally decidable. Commented Jul 6, 2022 at 2:30

2 Answers 2

3

@ITgoldman's explanation is right: a is retained because inner uses it, and fn === inner, and fn is still reachable. This is not a leak.

a can be reached again after fn() finishes simply by calling fn() again. b is just a local variable, so it goes out of scope when fn() returns.

An example how this is useful/required: consider the following slight modification of your snippet:

function outer() {
  let a = 0;

  return function inner() {
    a++;
    console.log(`counter is now ${a}`);
  };
};
const fn = outer(); 
fn()
fn()
fn()

Clearly, a should remain alive from one invocation of fn() to the next, so it can't be collected.

It doesn't matter whether inner/fn reads from a, writes to a, or does both. As long as it uses a in any way, a will remain alive as long as fn is alive. This makes sense because there could be more than one function referring to it: you could have inner1 allocating new arrays and storing them in a, and inner2 doing stuff with these arrays.

(This is not a V8 question; all spec-compliant JavaScript engines are required to behave like this.)

Sign up to request clarification or add additional context in comments.

11 Comments

got it. there are many posts claiming closure causes memory leaks... are they just wrong?
Any post claiming that your example is a memory leak is wrong, yes. It's required behavior.
interesting... but if I tried outer()() instead of const fn = outer(); fn() then there is no a retained according to the heap snapshot. Is this also by design?
Yes. As I said, fn keeps a alive. If there is no fn, a is cleaned up.
thanks. btw is this old thread related to this question? bugs.chromium.org/p/chromium/issues/detail?id=315190
|
0

Usually, a Lexical Environment is removed from memory with all the variables after the function call finishes. That’s because there are no references to it. As any JavaScript object, it’s only kept in memory while it’s reachable.

However, if there’s a nested function that is still reachable after the end of a function, then it has [[Environment]] property that references the lexical environment.

You can more about how garbage collection works in closures here.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.