javascript中的关闭和回调内存泄漏
Posted
技术标签:
【中文标题】javascript中的关闭和回调内存泄漏【英文标题】:Closure and callback memory leak in javascript 【发布时间】: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 在第一次回调后没有清理该闭包?
【参考方案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 函数发布一个特定的示例,您将获得特定的建议。以上是关于javascript中的关闭和回调内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章