[Memory leak] 3. Garbage collection in Closure

Zhentiw發表於2024-11-04

Example code:

function createIncrease() {
  const doms = new Array(100000).fill(0).map((_, i) => {
    const dom = document.createElement('div');
    dom.innerHTML = i;
    return dom;
  });

  function increase() {
    doms.forEach((dom) => {
      dom.innerHTML = Number(dom.innerHTML) + 1;
    });
  }

  return increase;
}

const increase = createIncrease();
const btn = document.querySelector('button');
btn.addEventListener('click', increase);

What is garbage?

Is domsvariable a garbage in above code?

No. It's not, because code need to use it.

Then how about this code? nums

<script>
  const nums = [1, 2, 3, 4, 5];
  const sum = nums.reduce((total, num) => total + num, 0);

  console.log(sum);
</script>

It's hard to say, due to you can console.log(nums)in console

What I want to point out is that, only developer know what is garbage we want to collect, the program can not tell in all cases.

The only thing program can consider it as garbage is the memory you can no longer access.

For example:

<script>
  let nums = [1, 2, 3, 4, 5];
  nums = [4,5,6,7]
  const sum = nums.reduce((total, num) => total + num, 0);

  console.log(sum);
</script>

Then program knows to cleanup [1, 2, 3, 4, 5]in memory, because there is no way to access this piece of data anymore.

What is memory leak?

For those piece of data, developer knows we don't need anymore, but still we are able to access those or we have references to it; then those will cause memory leak.

So the way to resolve this problem is make those piece of data unacessable again.

A simple example:

<script>
  let nums = [1, 2, 3, 4, 5];
  const sum = nums.reduce((total, num) => total + num, 0);

  console.log(sum);
  nums = null
</script>

In this end, we set nums = nullto tell GC to collect those memory.

Another example:

function createIncrease() {
  const doms = new Array(100000).fill(0).map((_, i) => {
    const dom = document.createElement('div');
    dom.innerHTML = i;
    return dom;
  });

  function increase() {
    doms.forEach((dom) => {
      dom.innerHTML = Number(dom.innerHTML) + 1;
    });
  }

  return increase;
}

const increase = createIncrease();
const btn = document.querySelector('button');
function handleClick() {
    increase()
    btn.removeEventListener('click', handleClick)
    increase = null
}
btn.addEventListener('click', handleClick);

We set increase = null, in this case, program can no longer access domsvariable. Then it should be collected by GC.

Memory leak in Closure

function createIncrease() {
  const doms = new Array(100000).fill(0).map((_, i) => {
    const dom = document.createElement('div');
    dom.innerHTML = i;
    return dom;
  });

  function increase() {
    doms.forEach((dom) => {
      dom.innerHTML = Number(dom.innerHTML) + 1;
    });
  }

  return increase;
}

const increase = createIncrease();
const btn = document.querySelector('button');
function handleClick() {
    increase()
    btn.removeEventListener('click', handleClick)
    increase = null
}
btn.addEventListener('click', handleClick);

This clousure example is hard to detect, normally we don't set increase = null. Due to increaseis just a function, how much memory it can takes anyhow?...

But for this function, it holds on a big chunk of data, then cause the memory leak.

Momory is not accessable but meanwhile cannot be GC collected?

This is possible, such as Last focused input/textarea element cause memory leak in Chrome

Another example:

function createIncrease() {
  const doms = new Array(100000).fill(0).map((_, i) => {
    const dom = document.createElement('div');
    dom.innerHTML = i;
    return dom;
  });

  function increase() {
  }
    
  function _temp() {
     doms
  }

  return increase;
}

let increase;
const btn = document.querySelector('button');
function handleClick() {
    increase = createIncrease()
}
btn.addEventListener('click', handleClick);

Now, increaseshas not access to doms, but a new function _tempwhich won't be called anyhow, but it holds on the reference to doms.

If we check the memory profile, we should see that the domswon't be GC collected.

Why?

Inside closure, if there is only one function increasewhich access doms, program will optimize syntactic environment to move away doms. In this case, it won't cause memory leak.

[Memory leak] 3. Garbage collection in Closure

Inside closure, increase & _tempthey share the same syntactic environment, in this case, GC won't optimize the domsvariables, and also won't be able to garbage collect it and will cause memory leak.
[Memory leak] 3. Garbage collection in Closure

當多個函式共享詞法環境時,會導致詞法環境膨脹,從而導致出現無法觸達也無法回收的記憶體空間

相關文章