[Memory leak] 3. Garbage collection in Closure
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 doms
variable 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 = null
to 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 doms
variable. 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 increase
is 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, increases
has not access to doms
, but a new function _temp
which won't be called anyhow, but it holds on the reference to doms
.
If we check the memory profile, we should see that the doms
won't be GC collected.
Why?
Inside closure, if there is only one function increase
which access doms
, program will optimize syntactic environment to move away doms
. In this case, it won't cause memory leak.
Inside closure, increase & _temp
they share the same syntactic environment, in this case, GC won't optimize the doms
variables, and also won't be able to garbage collect it and will cause memory leak.
当多个函数共享词法环境时,会导致词法环境膨胀,从而导致出现无法触达也无法回收的内存空间