涉及jQuery Ajax请求的内存泄漏

我有一个在IE8和Firefox中泄漏内存的网页; Windows Process Explorer中显示的内存使用量随着时间的推移不断增长。

以下页面请求“unplanned.json”url,这是一个永远不会更改的静态文件(尽管我将我的Cache-control HTTP标头设置为no-cache以确保Ajax请求始终通过)。 当它获得结果时,它清除HTML表,循环从服务器返回的json数组,并为数组中的每个条目动态地向HTML表添加一行。 然后等待2秒钟并重复此过程。

这是整个网页:

   Test Page     function kickoff() { $.getJSON("unplanned.json", resetTable); } function resetTable(rows) { $("#content tbody").empty(); for(var i=0; i<rows.length; i++) { $("" + "" + rows[i].mpe_name + "" + "" + rows[i].bin + "" + "" + rows[i].request_time + "" + "" + rows[i].filtered_delta + "" + "" + rows[i].failed_delta + "" + "").appendTo("#content tbody"); } setTimeout(kickoff, 2000); } $(kickoff);  
MPE Bin When Filtered Failed

如果它有帮助,这里是我发回的json的一个例子(它是这个确切的数组,有数千个条目而不是一个):

 [ { mpe_name: "DBOSS-995", request_time: "09/18/2009 11:51:06", bin: 4, filtered_delta: 1, failed_delta: 1 } ] 

编辑:我已经接受了Toran非常有帮助的答案,但我觉得我应该发布一些额外的代码,因为他的removefromdom jQuery插件有一些限制:

  • 它只删除单个元素。 所以你不能给它一个类似`$(“#content tbody tr”)`的查询,并期望它删除你指定的所有元素。
  • 使用它删除的任何元素都必须具有`id`属性。 因此,如果我想删除我的`tbody`,那么我必须将`id`分配给我的`tbody`标签,否则它会出错。
  • 它会删除元素本身及其所有后代,因此如果您只是想清空该元素,那么之后您将不得不重新创建它(或者将插件修改为空而不是删除)。

所以这是我上面的页面修改为使用Toran的插件。 为简单起见,我没有应用Peter提供的任何一般性能建议。 这是现在不再有内存泄漏的页面:

   Test Page       
MPE Bin When Filtered Failed

进一步编辑:我会保持我的问题不变,但值得注意的是,这个内存泄漏与Ajax无关。 事实上,以下代码内存泄漏只是相同,并且使用Toran的removefromdom jQuery插件就可以轻松解决:

 function resetTable() { $("#content tbody").empty(); for(var i=0; i<1000; i++) { $("#content tbody").append("" + "DBOSS-095" + "" + "" + 4 + "" + "" + "09/18/2009 11:51:06" + "" + "" + 1 + "" + "" + 1 + ""); } setTimeout(resetTable, 2000); } $(resetTable); 

我不知道为什么firefox不满意这个但我可以从经验中说,在IE6 / 7/8中你必须设置innerHTML =“”; 在您要从DOM中删除的对象上。 (如果你动态创建了这个DOM元素)

$("#content tbody").empty(); 可能不会释放这些动态生成的DOM元素。

而是尝试类似下面的东西(这是我写的一个jQuery插件来解决问题)。

 jQuery.fn.removefromdom = function(s) { if (!this) return; var bin = $("#IELeakGarbageBin"); if (!bin.get(0)) { bin = $("
"); $("body").append(bin); } $(this).children().each( function() { bin.append(this); document.getElementById("IELeakGarbageBin").innerHTML = ""; } ); this.remove(); bin.append(this); document.getElementById("IELeakGarbageBin").innerHTML = ""; };

您可以这样称呼: $("#content").removefromdom();

这里唯一的问题是,每次要构建表时都需要重新创建表。

此外,如果这确实解决了您在IE中的问题,您可以在我今年早些时候写的一篇博文中了解更多相关内容,当时我遇到了同样的问题。

编辑我上面的插件更新为95%免费的JavaScript现在使用更多的jQuery比以前的版本。 你仍然会注意到我必须使用innerHTML因为jQuery函数html(“”); IE6 / 7/8的行为不一样

我不确定泄漏,但你的resetTable()函数非常低效。 尝试先解决这些问题并查看最终结果。

  • 不要在循环中附加到DOM。 如果必须执行DOM操作,则附加到文档片段,然后将该片段移动到DOM。
  • 但是无论如何,innerHTML比DOM操作更快,所以如果可以,请使用它。
  • 将jQuery集存储到局部变量中 – 无需每次都重新运行选择器。
  • 还将重复引用存储在局部变量中。
  • 迭代任何排序的集合时,也将长度存储在局部变量中。

新代码:

   Test Page     
MPE Bin When Filtered Failed

参考文献:

  • jQuery性能规则
  • 加快你的Javascript

如果我错了,请纠正我,但SetTimeout(fn)是否阻止释放调用者的内存空间? 这样在resetTable(rows)方法期间分配的所有变量/内存都将保持分配,直到循环结束?

如果是这种情况,将字符串构造和appendTo逻辑推送到另一个方法可能会好一点,因为这些对象在每次调用后都会被释放,并且只返回返回值的内存(在这种情况下是字符串标记,如果是新方法使appendTo())保留在内存中。

在本质上:

初始召唤开始

– >调用resetTable()

– > – > SetTimeout再次调用启动

– > – > – >再次调用resetTable()

– > – > – > – >继续直到无限

如果代码永远不会真正解决,那么树将继续增长。

基于此释放一些内存的另一种方法就像下面的代码:

 function resetTable(rows) { appendRows(rows); setTimeout(kickoff, 2000); } function appendRows(rows) { var rowMarkup = ''; var length = rows.length var row; for (i = 0; i < length; i++) { row = rows[i]; rowMarkup += "" + "" + row.mpe_name + "" + "" + row.bin + "" + "" + row.request_time + "" + "" + row.filtered_delta + "" + "" + row.failed_delta + "" + ""; } $("#content tbody").html(rowMarkup); } 

这会将标记附加到你的tbody然后完成堆栈的那一部分。 我很确定“行”的每次迭代仍将保留在内存中; 但是,标记字符串等应该最终释放。

再一次……我已经有一段时间了,因为我在这个低级别看待SetTimeout所以我在这里完全错了。 在任何情况下,这都不会消除泄漏,只会降低生长速度。 这取决于使用的JavaScript引擎的垃圾收集器如何处理SetTimeout循环,就像你在这里一样。