一种因闭包引发的内存泄露
发布于 7 年前 作者 sodawy 5836 次浏览 来自 分享

很久以前的文

原文地址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 logMark-sweep后obj数量不断增加 gc log

debug查看堆栈,能看到引用关系与注释分析一致,someMethodscopes中有originalThing的引用,且不断点下去会一直循环有 debug查看引用关系,证明未被gc

解决

  1. replaceThing解决加入originalThing = null
  2. unused以参数形式,而非闭包使用originalThing
回到顶部