javascriptmemory-leaksgarbage-collection

Why "arr" can not be GC?


(() => {
  var arr = [];  // Memory leak 
  for (let i = 0; i < 10000000; i++) {
    arr.push(Math.random());
  }

  function emptyFn() {}
  function invoke(options) {
    window.__options = options;
  }

  (() => {
    emptyFn(arr)
  })()

  invoke({
    success: () => {},
    fail: () => {},
  });
})();

I run this code in Chrome, See the memory useage in devTools, the var arr never been GC


Solution

  • Two things come together here that seem to overwhelm the garbage collector's ability to detect that arr can be collected. This simpler example demonstrates it. (Tested in Chrome 135.0.7049.42.)

    (() => {
      var arr = []; // Memory leak
      for (let i = 0; i < 10000000; i++) {
        arr.push(Math.random());
      }
      window.__retainer = () => {}; /* remove this retainer line */
      (() => arr)(); /* replace this line with an alternative */
      /* arr; // alternative */
    })();

    First, arr is in the closure of a function that remains accessible from outside after the code has been executed (as window.__options.success in your example, as window.__retainer in mine), even though this function does not access arr. If this retainer is removed, arr can be collected.

    Second, arr is accessed inside that closure during the execution of a function that is not accessible from outside (this function execution is

    (() => {
      emptyFn(arr)
    })()
    

    in your example, and is (() => arr)() in mine). The garbage collector seems unable to recognize when arr can be forgotten after this function execution. If the line containing the function execution is replaced with the simpler alternative, then arr can be collected (despite the retainer).

    In the absence of the retainer, the garbage collector is able to "forget" arr after the function execution.