【问题标题】:Closure and callback memory leak in javascriptjavascript中的关闭和回调内存泄漏
【发布时间】:2013-05-02 18:33:53
【问题描述】:
function(foo, cb) {
  var bigObject = new BigObject();
  doFoo(foo, function(e) {
     if (e.type === bigObject.type) {
          cb();
          // bigObject = null;
     }
  });
}

上面的例子展示了一个经典的、偶然的(或者可能不是)内存泄漏的闭包。 V8 垃圾收集器无法确定删除 bigObject 是否安全,因为它正在用于可以多次调用的回调函数中。

一种解决方案是在回调函数中的作业结束时将bigObject 设置为null。但是如果你使用了很多变量(想象一下有n 变量,比如bigObject,并且它们都在回调中使用),那么清理这将成为一个丑陋的问题。

我的问题是:还有其他方法可以清理那些使用过的变量吗?

编辑 这是另一个(真实世界)示例:所以我从 mongodb 获取应用程序并将其与其他应用程序进行比较。来自 mongodb 的回调使用从该回调中定义的变量应用程序。从 mongodb 获得结果后,我也将它作为回调返回(因为它都是异步的,我不能只写 return )。所以实际上可能会发生我将回调一直传播到源......

function compareApplications(application, condition, callback) {

    var model = database.getModel('Application');
    model.find(condition, function (err, applicationFromMongo) {
        var result = (applicationFromMongo.applicationID == application.applicationID)
        callback(result)        
    }
}

【问题讨论】:

  • 让我问你这个 - 为什么这是一个问题? change 处理程序被多次调用。那么,除非您取消绑定 change 事件,否则您(或 GC)将如何知道何时真正结束使用 bigObject?您似乎想要一个 bigObject 实例,以便处理程序可以比较类型。您将其实例化一次,这样每次处理程序运行时都会减少负载。如果你想清理它,每次都在处理程序中实例化它,或者期望它“泄漏”内存,因为它就是这样工作的。
  • 用 .one() 代替 .on() 怎么样?
  • 请注意,我不使用 jQuery,也不绑定或注册某些事件。我只是将回调函数传递给另一个。我使用 NODE.JS !
  • 我担心的是,我可能有一系列回调......例如,业务函数调用数据函数调用 mongodb,并且这些函数之间的所有通信都在回调中。也许来自 mongodb 的回调确实可以清除所有内容,但业务和数据层中的这些其他回调是什么。我想 GC 只看到回调,而他看不到他下面的东西。我还没有对泄漏进行分析,但我可以清楚地看到我的 node.js 工作人员使用了太多的 rss(RAM) 内存。
  • 是的,但是在每一种回调中(在业务层或数据层)你都可以有一个闭包,你真的确定他们会清理吗? ...听听这个例子。在代码的其他部分中,我也从数据库中读取但在流中。我有 stream.on(data){...} 事件,每次当我收集 1000 条记录时,我都会为业务层启动回调(在我的第一篇文章中的示例中,它将是这个:回调(结果))...业务层回调函数的实现实际上是一个闭包,它工作得很好。为什么 GC 在第一次回调后没有清理该闭包?

标签: javascript node.js memory-leaks closures


【解决方案1】:

以 Brandon 的回答为基础:如果(出于某种可怕的原因)您无法取消订阅回调,您始终可以自己处理删除回调:

function createSingleUseCallback(callback)
{
    function callbackWrapper()
    {
        var ret = callback.apply(this, arguments);
        delete callback;
        return ret;
    }
    return callbackWrapper;
}

function compareApplications(application, condition, callback)
{
    var model = database.getModel('Application');
    model.find(condition, createSingleUseCallback(function (err, applicationFromMongo)
    {
        var result = (applicationFromMongo.applicationID == application.applicationID);
        callback(result);
    })
}

【讨论】:

    【解决方案2】:

    如果你的回调函数应该只被调用一次,那么你应该在它被调用后取消订阅。这将释放你的回调 + 对 GC 的闭包。随着您的关闭发布,bigObject 也将免费被 GC 收集。

    这是最好的解决方案 - 正如您所指出的,GC 不会神奇地知道您的回调只会被调用一次。

    【讨论】:

    • 感谢您的回答,但您能举一些取消订阅回调的例子吗?
    • 这取决于您使用什么机制来订阅回调。您没有提供足够的详细信息来说明 doFoo() 的作用。大多数框架要么提供undoFoo() 方法,要么doFoo() 本身返回一个您可以调用以取消订阅的方法。如果您使用 jQuery,您将使用 $("...").on("change", cb); 订阅并使用 $("...").off("change", cb) 取消订阅。事实上,他们有一种方法可以针对您只想订阅 1 个事件的确切情况:$("...").one("change", cb),它会在一次调用后自动取消订阅。
    • 请注意我在第一篇文章中更改了示例。这现在更像是我的实际问题。我将回调函数传递给另一个函数。我使用 node.js。 Foo 只是一个例子,但问题与我在现实世界中的功能相同。回调函数在范围内使用变量,GC 无法隐式清理它:( ...我的 rss 内存增长得非常快,因为我有很多流量。
    • 问题是我不知道如何在这个特定的问题中从回调中注销!正如我所说,我使用 node.js 并且我没有绑定到像 jQuery 或类似的事件,我只是将回调函数传递给另一个函数(在这个例子中是 doFoo)并且 doFoo 做一些 IO 操作,然后在我传递的某个点调用回调函数。
    • Node.Js 没有名为 doFoo 的函数。如果你不想给我们一个具体的例子,那么我只能给出一般性的建议,基本上就是 RTFM。使用特定的 node.js 函数发布一个特定的示例,您将获得特定的建议。
    猜你喜欢
    • 2013-03-29
    • 2013-07-12
    • 1970-01-01
    • 1970-01-01
    • 2013-05-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多