简体   繁体   English

涉及jQuery Ajax请求的内存泄漏

[英]Memory leak involving jQuery Ajax requests

I have a webpage that's leaking memory in both IE8 and Firefox; 我有一个网页正在IE8和Firefox中泄漏内存; the memory usage displayed in the Windows Process Explorer just keeps growing over time. Windows Process Explorer中显示的内存使用量只是随着时间的推移而不断增长。

The following page requests the "unplanned.json" url, which is a static file that never changes (though I do set my Cache-control HTTP header to no-cache to make sure that the Ajax request always goes through). 以下页面请求“ unplanned.json” URL,这是一个永不更改的静态文件(尽管我确实将Cache-control HTTP标头设置为no-cache以确保Ajax请求始终通过)。 When it gets the results, it clears out an HTML table, loops over the json array it got back from the server, and dynamically adds a row to an HTML table for each entry in the array. 当得到结果时,它将清除HTML表,循环从服务器返回的json数组,并为数组中的每个条目动态地向HTML表添加一行。 Then it waits 2 seconds and repeats this process. 然后等待2秒钟,然后重复此过程。

Here's the entire webpage: 这是整个网页:

<html> <head>
    <title>Test Page</title>
    <script type="text/javascript"
     src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js"></script>
</head> <body>
<script type="text/javascript">
    function kickoff() {
        $.getJSON("unplanned.json", resetTable);
    }
    function resetTable(rows) {
        $("#content tbody").empty();
        for(var i=0; i<rows.length; i++) {
            $("<tr>"
                + "<td>" + rows[i].mpe_name + "</td>"
                + "<td>" + rows[i].bin + "</td>"
                + "<td>" + rows[i].request_time + "</td>"
                + "<td>" + rows[i].filtered_delta + "</td>"
                + "<td>" + rows[i].failed_delta + "</td>"
            + "</tr>").appendTo("#content tbody");
        }
        setTimeout(kickoff, 2000);
    }
    $(kickoff);
</script>
<table id="content" border="1" style="width:100% ; text-align:center">
<thead><tr>
    <th>MPE</th> <th>Bin</th> <th>When</th> <th>Filtered</th> <th>Failed</th>
</tr></thead>
<tbody></tbody>
</table>
</body> </html>

If it helps, here's an example of the json I'm sending back (it's this exact array wuith thousands of entries instead of just one): 如果有帮助,这是我发回的json的示例(这是一个精确的数组,包含数千个条目,而不只是一个条目):

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

EDIT: I've accepted Toran's extremely helpful answer, but I feel I should post some additional code, since his removefromdom jQuery plugin has some limitations: 编辑:我已经接受了Toran的非常有帮助的答案,但是我觉得我应该发布一些其他代码,因为他的removefromdom jQuery插件有一些限制:

  • It only removes individual elements. 它仅删除单个元素。 So you can't give it a query like `$("#content tbody tr")` and expect it to remove all of the elements you've specified. 因此,您不能给它一个类似$(“#content tbody tr”)`的查询,并且不能期望它删除您指定的所有元素。
  • Any element that you remove with it must have an `id` attribute. 您删除的所有元素都必须具有id属性。 So if I want to remove my `tbody`, then I must assign an `id` to my `tbody` tag or else it will give an error. 因此,如果我想删除我的`tbody`,则必须将`id`分配给我的`tbody`标签,否则它将给出错误。
  • It removes the element itself and all of its descendants, so if you simply want to empty that element then you'll have to re-create it afterwards (or modify the plugin to empty instead of remove). 它删除了元素本身及其所有后代,因此,如果您只想清空该元素,则必须在之后重新创建它(或将插件修改为空而不是删除)。

So here's my page above modified to use Toran's plugin. 因此,这是我上面修改过的页面,以使用Toran的插件。 For the sake of simplicity I didn't apply any of the general performance advice offered by Peter . 为简单起见,我没有应用Peter提供的任何一般性能建议。 Here's the page which now no longer memory leaks: 这是现在不再内存泄漏的页面:

<html>
<head>
    <title>Test Page</title>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js"></script>
</head>
<body>
<script type="text/javascript">
<!--
    $.fn.removefromdom = function(s) {
        if (!this) return;

        var el = document.getElementById(this.attr("id"));

        if (!el) return;

        var bin = document.getElementById("IELeakGarbageBin");

        //before deleting el, recursively delete all of its children.
        while (el.childNodes.length > 0) {
            if (!bin) {
                bin = document.createElement("DIV");
                bin.id = "IELeakGarbageBin";
                document.body.appendChild(bin);
            }

            bin.appendChild(el.childNodes[el.childNodes.length - 1]);
            bin.innerHTML = "";
        }

        el.parentNode.removeChild(el);

        if (!bin) {
            bin = document.createElement("DIV");
            bin.id = "IELeakGarbageBin";
            document.body.appendChild(bin);
        }

        bin.appendChild(el);
        bin.innerHTML = "";
    };

    var resets = 0;
    function kickoff() {
        $.getJSON("unplanned.json", resetTable);
    }
    function resetTable(rows) {
        $("#content tbody").removefromdom();
        $("#content").append('<tbody id="id_field_required"></tbody>');
        for(var i=0; i<rows.length; i++) {
            $("#content tbody").append("<tr><td>" + rows[i].mpe_name + "</td>"
                + "<td>" + rows[i].bin + "</td>"
                + "<td>" + rows[i].request_time + "</td>"
                + "<td>" + rows[i].filtered_delta + "</td>"
                + "<td>" + rows[i].failed_delta + "</td></tr>");
        }
        resets++;
        $("#message").html("Content set this many times: " + resets);
        setTimeout(kickoff, 2000);
    }
    $(kickoff);
// -->
</script>
<div id="message" style="color:red"></div>
<table id="content" border="1" style="width:100% ; text-align:center">
<thead><tr>
    <th>MPE</th>
    <th>Bin</th>
    <th>When</th>
    <th>Filtered</th>
    <th>Failed</th>
</tr></thead>
<tbody id="id_field_required"></tbody>
</table>
</body>
</html>

FURTHER EDIT: I'll leave my question unchanged, though it's worth noting that this memory leak has nothing to do with Ajax. 进一步编辑:我将保留我的问题不变,尽管值得注意的是,此内存泄漏与Ajax无关。 In fact, the following code would memory leak just the same and be just as easily solved with Toran's removefromdom jQuery plugin: 实际上,以下代码将发生相同的内存泄漏,并且可以使用Toran的removefromdom jQuery插件轻松解决:

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

I'm not sure why firefox isn't happy w/ this but I can say from experience that in IE6/7/8 you must set innerHTML = ""; 我不确定为什么firefox对此不满意,但是我可以根据经验说在IE6 / 7/8中必须设置innerHTML =“”; on the object you want removed from the DOM. 要从DOM中删除的对象上。 (if you created this DOM element dynamically that is) (如果您是动态创建此DOM元素的,则是)

$("#content tbody").empty(); might not be releasing these dynamically generated DOM elements. 可能不会释放这些动态生成的DOM元素。

Instead try something like the below (this is a jQuery plugin I wrote to solve the issue). 而是尝试如下所示的方法(这是我为解决该问题而编写的jQuery插件)。

jQuery.fn.removefromdom = function(s) {
    if (!this) return;

    var bin = $("#IELeakGarbageBin");

    if (!bin.get(0)) {
        bin = $("<div id='IELeakGarbageBin'></div>");
        $("body").append(bin);
    }

    $(this).children().each(
            function() {
                bin.append(this);
                document.getElementById("IELeakGarbageBin").innerHTML = "";
            }
    );

    this.remove();

    bin.append(this);
    document.getElementById("IELeakGarbageBin").innerHTML = "";
};

You would call this like so: $("#content").removefromdom(); 您可以这样称呼: $("#content").removefromdom();

The only issue here is that you need to re-create your table each time you want to build it. 唯一的问题是您每次要构建表时都需要重新创建表。

Also, if this does solve your issue in IE you can read more about this in a blog post that I wrote earlier this year when I came across the same problem. 另外,如果这确实解决了IE中的问题,则可以在我今年初遇到相同问题时写的博客文章中了解有关此问题的更多信息。

Edit I updated the plugin above to be 95% JavaScript free now so it's using more jQuery than the previous version. 编辑我现在将上面的插件更新为95%免费JavaScript,因此它比以前的版本使用更多的jQuery。 You will still notice that I have to use innerHTML because the jQuery function html(""); 您仍然会注意到我必须使用innerHTML,因为jQuery函数html(“”); doesn't act the same for IE6/7/8 对于IE6 / 7/8的作用不同

I'm not sure about the leak, but your resetTable() function is very inefficient. 我不确定泄漏,但是您的resetTable()函数效率很低。 Try fixing those problems first and see where you end up. 首先尝试解决这些问题,然后看看最终的结果。

  • Don't append to the DOM in a loop. 不要在循环中附加到DOM。 If you must do DOM manipulation, then append to a document fragment, and then move that fragment to the DOM. 如果必须执行DOM操作,则将其附加到文档片段,然后将该片段移至DOM。
  • But innerHTML is faster than DOM manipulation anyway, so use that if you can. 但是innerHTML总是比DOM操作快,因此请尽可能使用它。
  • Store jQuery sets into local variables - no need to re-run the selector every time. 将jQuery集存储到局部变量中-无需每次都重新运行选择器。
  • Also store repeated references in a local variable. 还将重复的引用存储在局部变量中。
  • When iterating over a collection of any sort, store the length in a local variable, too. 遍历任何种类的集合时,也将长度存储在局部变量中。

New code: 新代码:

<html> <head>
    <title>Test Page</title>
    <script type="text/javascript"
     src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js"></script>
</head> <body>
<script type="text/javascript">
$(function()
{
    var $tbody = $("#content tbody");

    function kickoff() {
        $.getJSON("test.php", resetTable);
    }

    function resetTable(rows)
    {
        var html = ''
          , i = 0
          , l = rows.length
          , row;
        for ( ; i < l; i++ )
        {
            row = rows[i];
            html += "<tr>"
                + "<td>" + row.mpe_name + "</td>"
                + "<td>" + row.bin + "</td>"
                + "<td>" + row.request_time + "</td>"
                + "<td>" + row.filtered_delta + "</td>"
                + "<td>" + row.failed_delta + "</td>"
            + "</tr>";
        }
        $tbody.html( html );
        setTimeout(kickoff, 2000);
    }

    kickoff();
});
</script>
<table id="content" border="1" style="width:100% ; text-align:center">
<thead>
    <th>MPE</th> <th>Bin</th> <th>When</th> <th>Filtered</th> <th>Failed</th>
</thead>
<tbody></tbody>
</table>
</body> </html>

References: 参考文献:

Please correct me if I'm wrong here but doesn't SetTimeout(fn) prevents the release of the caller's memory space? 如果我在这里错了,请纠正我,但是SetTimeout(fn)不能阻止调用者的内存空间的释放吗? So that all variables/memory allocated during the resetTable(rows) method would remain allocated until the loop finished? 这样,在resetTable(rows)方法期间分配的所有变量/内存将保持分配状态,直到循环完成为止?

If that's the case, pushing string construction and appendTo logic to a different method may be a little better since those objects would get freed up after each calling and only the memory of the returned value (in this case either the string markup or nothing if the new method did the appendTo()) would remain in memory. 如果是这种情况,将字符串构造和appendTo逻辑推到其他方法可能会更好一些,因为这些对象将在每次调用后释放,并且只保留返回值的内存(在这种情况下为字符串标记,否则为空。这个新方法确实将appendTo())保留在内存中。

In essence: 在本质上:

Initial Call to kickoff 首次开球

-> Calls resetTable() ->调用resetTable()

-> -> SetTimeout Calls kickoff again ->-> SetTimeout再次调用启动

-> -> -> Calls resetTable() again ->->->再次调用resetTable()

-> -> -> -> Continue until Infinite ->->->->继续直到无限

If the code never truly resolves, the tree would continue to grow. 如果代码从未真正解析过,那么树将继续增长。

An alternative way of freeing up some memory based on this would be like the below code: 基于此释放内存的另一种方法将类似于以下代码:

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 += "<tr>"
                + "<td>" + row.mpe_name + "</td>"
                + "<td>" + row.bin + "</td>"
                + "<td>" + row.request_time + "</td>"
                + "<td>" + row.filtered_delta + "</td>"
                + "<td>" + row.failed_delta + "</td>"
                + "</tr>";      
    }

    $("#content tbody").html(rowMarkup);
}

This will append the markup to your tbody and then finish that part of the stack. 这会将标记附加到您的躯干,然后完成堆栈的该部分。 I am pretty sure that each iteration of "rows" will still remain in memory; 我很确定“行”的每次迭代仍将保留在内存中; however, the markup string and such should free up eventually. 但是,标记字符串等应最终释放。

Again...it's been a while since I've looked at SetTimeout at this low level so I could be completely wrong here. 再次...已经有一段时间了,因为我一直在这个低水平上查看SetTimeout,所以我在这里可能是完全错误的。 In anycase, this won't remove the leak and only possibly decrease the rate of growth. 无论如何,这不会消除泄漏,只会降低增长率。 It depends on how the garbage collector of the JavaScript engines in use deal with SetTimeout loops like you have here. 这取决于正在使用的JavaScript引擎的垃圾收集器如何处理SetTimeout循环(如您在此处所述)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM