Garbage collection and closures

GC within a function doesn’t work like I expected


This content originally appeared on Jake Archibald's blog and was authored by Jake Archibald's blog

Me, Surma, and Jason were hacking on a thing, and discovered that garbage collection within a function doesn't quite work like we expected.

function demo() {
  const bigArrayBuffer = new ArrayBuffer(100_000_000);
  const id = setTimeout(() => {
    console.log(bigArrayBuffer.byteLength);
  }, 1000);

  return () => clearTimeout(id);
}

globalThis.cancelDemo = demo();

With the above, bigArrayBuffer is leaked forever. I didn't expect that, because:

  • After a second, the function referencing bigArrayBuffer is no longer callable.
  • The returned cancel function doesn't reference bigArrayBuffer.

But that doesn't matter. Here's why:

JavaScript engines are reasonably smart

This doesn't leak:

function demo() {
  const bigArrayBuffer = new ArrayBuffer(100_000_000);
  console.log(bigArrayBuffer.byteLength);
}

demo();

The function executes, bigArrayBuffer is no longer needed, so it's garbage collected.

This also doesn't leak:

function demo() {
  const bigArrayBuffer = new ArrayBuffer(100_000_000);

  setTimeout(() => {
    console.log(bigArrayBuffer.byteLength);
  }, 1000);
}

demo();

In this case:

  1. The engine knows it needs to retain bigArrayBuffer beyond the initial execution of the function, so it's kept around. It's associated with the scope that was created when demo() was called.
  2. After a second, the function referencing bigArrayBuffer is no longer callable.
  3. Since nothing within the scope is callable, the scope is garbage collected, along with bigArrayBuffer.

This also doesn't leak:

function demo() {
  const bigArrayBuffer = new ArrayBuffer(100_000_000);

  const id = setTimeout(() => {
    console.log('hello');
  }, 1000);

  return () => clearTimeout(id);
}

globalThis.cancelDemo = demo();

In this case, the engine knows it doesn't need to retain bigArrayBuffer beyond the initial execution of the function, as none of the future-callables access it.

The problem case

Here's where it gets messy:

function demo() {
  const bigArrayBuffer = new ArrayBuffer(100_000_000);

  const id = setTimeout(() => {
    console.log(bigArrayBuffer.byteLength);
  }, 1000);

  return () => clearTimeout(id);
}

globalThis.cancelDemo = demo();

This leaks, because:

  1. The engine knows it needs to retain bigArrayBuffer beyond the initial execution of the function, so it's kept around. It's associated with the scope that was created when demo() was called.
  2. After a second, the function referencing bigArrayBuffer is no longer callable.
  3. But, the scope remains, because the cleanup function within is still callable.
  4. bigArrayBuffer is associated with the scope, so it remains in memory.

I thought engines would be smarter, and GC bigArrayBuffer since it's no longer referenceable, but that isn't the case.

globalThis.cancelDemo = null;

Now bigArrayBuffer is GC'd, since nothing within the scope is callable.

This isn't specific to timers, it's just how I encountered the issue. For example:

function demo() {
  const bigArrayBuffer = new ArrayBuffer(100_000_000);

  globalThis.innerFunc1 = () => {
    console.log(bigArrayBuffer.byteLength);
  };

  globalThis.innerFunc2 = () => {
    console.log('hello');
  };
}

demo();
// bigArrayBuffer is retained, as expected.

globalThis.innerFunc1 = undefined;
// bigArrayBuffer is still retained, as unexpected.

globalThis.innerFunc2 = undefined;
// bigArrayBuffer can now be collected.

TIL!


This content originally appeared on Jake Archibald's blog and was authored by Jake Archibald's blog


Print Share Comment Cite Upload Translate Updates
APA

Jake Archibald's blog | Sciencx (2024-07-30T01:00:00+00:00) Garbage collection and closures. Retrieved from https://www.scien.cx/2024/07/30/garbage-collection-and-closures/

MLA
" » Garbage collection and closures." Jake Archibald's blog | Sciencx - Tuesday July 30, 2024, https://www.scien.cx/2024/07/30/garbage-collection-and-closures/
HARVARD
Jake Archibald's blog | Sciencx Tuesday July 30, 2024 » Garbage collection and closures., viewed ,<https://www.scien.cx/2024/07/30/garbage-collection-and-closures/>
VANCOUVER
Jake Archibald's blog | Sciencx - » Garbage collection and closures. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2024/07/30/garbage-collection-and-closures/
CHICAGO
" » Garbage collection and closures." Jake Archibald's blog | Sciencx - Accessed . https://www.scien.cx/2024/07/30/garbage-collection-and-closures/
IEEE
" » Garbage collection and closures." Jake Archibald's blog | Sciencx [Online]. Available: https://www.scien.cx/2024/07/30/garbage-collection-and-closures/. [Accessed: ]
rf:citation
» Garbage collection and closures | Jake Archibald's blog | Sciencx | https://www.scien.cx/2024/07/30/garbage-collection-and-closures/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.