解释由Addy Osmani粉碎杂志中的关闭内存泄漏示例

时间:2013-10-21 21:32:26

标签: javascript performance javascript-events garbage-collection

我正在关闭部分阅读addy osmani memory efficient JS粉碎杂志的博客。我理解以下函数保存对largeStr的引用,GC不能声明它。

var a = function () {
   var largeStr = new Array(1000000).join('x');
   return function () {
     return largeStr;
    };
}();

他在这里提到的解决方案没有对largeStr的引用,GC可以声称它。相反,他使用smallStr。

var a = function () {
    var smallStr = 'x';
    var largeStr = new Array(1000000).join('x');
    return function (n) {
        return smallStr;
    };
}();

我得到了Addy的观点,即没有引用大事。但是,我想知道是否有更好的方法,我可以拥有第一个功能的功能,并使其内存效率。

2 个答案:

答案 0 :(得分:9)

第一个函数创建largeStr并返回引用它的函数。因此,垃圾收集器无法释放largeStr是合乎逻辑的,因为它现在仍被a变量中包含的函数使用。

第二个函数没有对largeStr的持久引用,因此垃圾收集器可以释放它。

听起来你在问是否有办法保持对大的东西的引用,但却没有使用那种记忆。对此的答案是否定的。

此外,这些在技术上根本不是“泄漏”。它们是合法的内存使用。


您可以通过不预先构建大字符串来获得第一个没有内存使用的功能。如果你根据需要构建它,那么在有人调用该函数之前它将不会消耗任何内存。这显然是执行速度和内存使用之间的直接权衡,但这是你在这里的选择。如果它是预先缓存的,那么它会消耗内存。如果它仅根据需求构建,那么在使用之前它不会占用内存。

这是基于需求的内置版本,在使用之前不消耗内存:

var a = function () {
   return function () {
     return new Array(1000000).join('x');
    };
}();

这不必写得太过。。它也可以是这个,因为没有关闭:

var a = function() {
    return new Array(1000000).join('x');
}

这两个版本的缺点是,每次调用a()时都会创建字符串,但优点是永远不会永久缓存任何内容。完成a()的所有使用后,一切都被垃圾收集。


或者,只在首次使用时缓存它:

var a = function () {
   var largeStr;
   return function () {
     if (!largeStr) {
         largeStr = new Array(1000000).join('x');
     }
     return largeStr;
    };
}();

这样做的好处是,在第一次调用a()之前不会消耗内存,并且对a()的后续调用不必重新创建大字符串,但是单个largeStr将永远不会被垃圾回收它被创造了。

哪个最好取决于您的使用模式,以及在您的设计/使用中哪个权衡更重要。

答案 1 :(得分:1)

您需要非常慎重的设置才能导致V8闭包内存泄漏:

function create() {
    var a = "x";
    var b = new Array(1000000).join('x');

    //Force context-allocation for b
    (function(){b;});

    return function() {
        return a;
    };
}

window.a = create();
代码无法访问

b,但在您完全摆脱window.a之前无法收集它:

http://jsfiddle.net/bwPVe/

如果你在chrome中堆积快照,你会看到。