一种因闭包引发的内存泄露
很久以前的文
原文地址https://blog.meteor.com/an-interesting-kind-of-javascript-memory-leak-8b47d2e7f156
代码
无注释版 代码:
var theThing = null;
var replaceThing = function () {
var originalThing = theThing;
var unused = function () {
if(originalThing) {}
};
theThing = {
longStr: Date.now() + Array(1000000).join('*'),
someMethod: function () {}
};
};
setInterval(replaceThing, 100);
有注释版代码:
var theThing = null;
var replaceThing = function () {
var originalThing = theThing;
// Define a closure that references originalThing but doesn't ever actually get called.
// But because this closure exists, originalThing will be in the lexical environment for all closures defined in replaceThing, instead of being optimized out of it.
// If you remove this function, there is no leak.
// 定义一个闭包引用originalThing,但并不执行这个闭包function。
// 由于闭包的存在,originalThing会在挂到当前作用域下全部闭包的作用域中,而不是随着replaceThing退出而变得无引用
// 去掉unused 或者 去掉unused中的originalThing,就不会有内存泄露了
var unused = function () {
if(originalThing) {}
};
theThing = {
longStr: Date.now() + Array(1000000).join('*'),
// While originalThing is theoretically accessible by this function, it obviously doesn't use it.
// But because originalThing is part of the lexical environment,
// someMethod will hold a reference to originalThing,
// and so even though we are replacing theThing with something that has no effective way to reference the old value of theThing,
// the old value will never get cleaned up!
// 理论上someMethod可以访问originalThing,但他没有访问
// 但因为originalThing已经被挂到了someMethod的作用域
// 所以第n+1次setInterval的第3行,引用了第n次中的theTing,其中someMethod的作用域中有第n次的originalThing的引用
// 好像蛇头吃蛇尾,引用链永远不断,gc无法回收,形成内存泄露
someMethod: function () {}
};
// If you add `originalThing = null` here, there is no leak.
//debugger
};
setInterval(replaceThing, 100);
实际运行
开--inspect --trace-gc
运行一会,可以明显看到gc log
中Mark-sweep
后obj数量不断增加
debug查看堆栈,能看到引用关系与注释分析一致,someMethod
的scopes
中有originalThing
的引用,且不断点下去会一直循环有
解决
replaceThing
解决加入originalThing = null
unused
以参数形式,而非闭包使用originalThing