闭包词法环境中的 JavaScript 内存泄漏

标签 javascript memory memory-leaks garbage-collection

我试图理解为什么下面的代码会导致内存泄漏

var aThing = null;
var outer = function() {

    console.log('running');
    var something = aThing;

    var closure1 = function() {
        if (something) {
            console.log('something');
        }
    };

    aThing = {
        str: new Array(1000000).join('8'),
        someMethod: function() {}
    };
};
setInterval(outer, 1000);

这是显示内存从 Google Chrome 增加的时间线:

Memory Leak

但是这段代码的微小变化不会导致相同的内存泄漏:

var aThing = null;
var outer = function() {

    console.log('running');
    var something = aThing;
    var closure1 = function() {
        if (something) {
            console.log('something');
        }
    }

    aThing = {
        str: new Array(1000000).join('8')
    };

    function someMethod() {};
};
setInterval(outer, 1000);

这是显示 GC 清理正常的等效时间线。

No leak

我知道在第一个版本中存在内存泄漏,因为变量“something”没有被清理。为什么它在第二个示例中被 GC 而不是第一个?

最佳答案

主要的答案是,在您的第二个代码块中,没有直接引用任何一个闭包(closure1someMethod)在 outer< 的返回后仍然存在(outer 之外的任何内容都指向它们),因此没有任何内容指向创建它们的上下文,并且可以清除该上下文。但是,在您的第二个代码块中,对 someMethod 的直接引用在返回后仍然存在,作为您分配给 aThing 的对象的一部分,因此上下文作为整体不能被 GC。

让我们看看您的第一个区 block 会发生什么:

在第一次执行 outer 之后,我们有(忽略一堆细节):

            +−−−−−−−−−−−−−+
aThing−−−−−>| (object #1) |     
            +−−−−−−−−−−−−−+     
            | str: ...    |     +−−−−−−−−−−−−−−−−−−−−+
            | someMethod  |−−−−>| (context #1)       |
            +−−−−−−−−−−−−−+     +−−−−−−−−−−−−−−−−−−−−+
                                | something: null    |
                                | closure1: function |
                                +−−−−−−−−−−−−−−−−−−−−+

第二次执行后:

            +−−−−−−−−−−−−−+
aThing−−−−−>| (object #2) |     
            +−−−−−−−−−−−−−+     
            | str: ...    |     +−−−−−−−−−−−−−−−−−−−−+     
            | someMethod  |−−−−>| (context #2)       |     
            +−−−−−−−−−−−−−+     +−−−−−−−−−−−−−−−−−−−−+     +−−−−−−−−−−−−−+                           
                                | something          |−−−−>| (object #1) |                           
                                | closure1: function |     +−−−−−−−−−−−−−+                           
                                +−−−−−−−−−−−−−−−−−−−−+     | str: ...    |     +−−−−−−−−−−−−−−−−−−−−+
                                                           | someMethod  |−−−−>| (context #1)       |
                                                           +−−−−−−−−−−−−−+     +−−−−−−−−−−−−−−−−−−−−+
                                                                               | something: null    |
                                                                               | closure1: function |
                                                                               +−−−−−−−−−−−−−−−−−−−−+

第三次执行之后:

            +−−−−−−−−−−−−−+
aThing−−−−−>| (object #3) |     
            +−−−−−−−−−−−−−+     
            | str: ...    |     +−−−−−−−−−−−−−−−−−−−−+     
            | someMethod  |−−−−>| (context #3)       |     
            +−−−−−−−−−−−−−+     +−−−−−−−−−−−−−−−−−−−−+     +−−−−−−−−−−−−−+                                                                          
                                | something          |−−−−>| (object #2) |                                                                          
                                | closure1: function |     +−−−−−−−−−−−−−+                                                                          
                                +−−−−−−−−−−−−−−−−−−−−+     | str: ...    |     +−−−−−−−−−−−−−−−−−−−−+                                               
                                                           | someMethod  |−−−−>| (context #2)       |                                               
                                                           +−−−−−−−−−−−−−+     +−−−−−−−−−−−−−−−−−−−−+     +−−−−−−−−−−−−−+                           
                                                                               | something          |−−−−>| (object #1) |                           
                                                                               | closure1: function |     +−−−−−−−−−−−−−+                           
                                                                               +−−−−−−−−−−−−−−−−−−−−+     | str: ...    |     +−−−−−−−−−−−−−−−−−−−−+
                                                                                                          | someMethod  |−−−−>| (context #1)       |
                                                                                                          +−−−−−−−−−−−−−+     +−−−−−−−−−−−−−−−−−−−−+
                                                                                                                              | something: null    |
                                                                                                                              | closure1: function |
                                                                                                                              +−−−−−−−−−−−−−−−−−−−−+

你可以看到这是怎么回事。

由于第二个 block 从不保留对 closure1someMethod 的引用,因此它们都不会在内存中保留上下文。

最初在 2015 年回答您的问题时,我有点惊讶 V8(Chrome 的 JavaScript 引擎)没有优化这个泄漏,因为只保留了 someMethod,而 someMethod 实际上并没有使用 somethingclosure1(或 evalnew Functiondebugger)。虽然理论上它通过上下文引用了它们,但静态分析会表明它们实际上无法使用,因此可以删除。但是闭包优化真的很容易被打扰,我猜里面有什么东西在打扰它,或者 V8 团队发现做那种级别的分析不值得运行时成本。我确实记得看到来自 V8 团队之一的推文说它过去比现在做更多的闭包优化(这次编辑是在 2021 年 9 月),因为这种权衡是不值得的。

关于闭包词法环境中的 JavaScript 内存泄漏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34480663/

相关文章:

javascript - JavaScript setInterval() 方法会导致内存泄漏吗?

javascript - 在不同行中显示两个动态添加的元素

javascript - 通过javascript检测来自asp的ajax请求的结束

c# - 在 ASP.net C# 中,如何将参数从 Javascript 函数传递给 onclick 处理程序

c++ - C中的内存分配过程?是自上而下还是自下而上的方法

c - Little-endian 字节顺序(在 C 中)

javascript - API 浏览器/ Playground /沙箱,用于用 Javascript 编写的自定义 API

java - 这是内存泄漏还是我刚刚达到了内存中可以保留的对象的限制?

java - openGL/JOGL : Why do some Textures render "Smoothed" and some not?

android - 用于检查 android 中的内存泄漏的工具