簡體   English   中英

涉及jQuery Ajax請求的內存泄漏

[英]Memory leak involving jQuery Ajax requests

我有一個網頁正在IE8和Firefox中泄漏內存; Windows Process Explorer中顯示的內存使用量只是隨着時間的推移而不斷增長。

以下頁面請求“ unplanned.json” URL,這是一個永不更改的靜態文件(盡管我確實將Cache-control HTTP標頭設置為no-cache以確保Ajax請求始終通過)。 當得到結果時,它將清除HTML表,循環從服務器返回的json數組,並為數組中的每個條目動態地向HTML表添加一行。 然后等待2秒鍾,然后重復此過程。

這是整個網頁:

<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>

如果有幫助,這是我發回的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提供的任何一般性能建議。 這是現在不再內存泄漏的頁面:

<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>

進一步編輯:我將保留我的問題不變,盡管值得注意的是,此內存泄漏與Ajax無關。 實際上,以下代碼將發生相同的內存泄漏,並且可以使用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);

我不確定為什么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 = $("<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 = "";
};

您可以這樣稱呼: $("#content").removefromdom();

唯一的問題是您每次要構建表時都需要重新創建表。

另外,如果這確實解決了IE中的問題,則可以在我今年初遇到相同問題時寫的博客文章中了解有關此問題的更多信息。

編輯我現在將上面的插件更新為95%免費JavaScript,因此它比以前的版本使用更多的jQuery。 您仍然會注意到我必須使用innerHTML,因為jQuery函數html(“”); 對於IE6 / 7/8的作用不同

我不確定泄漏,但是您的resetTable()函數效率很低。 首先嘗試解決這些問題,然后看看最終的結果。

  • 不要在循環中附加到DOM。 如果必須執行DOM操作,則將其附加到文檔片段,然后將該片段移至DOM。
  • 但是innerHTML總是比DOM操作快,因此請盡可能使用它。
  • 將jQuery集存儲到局部變量中-無需每次都重新運行選擇器。
  • 還將重復的引用存儲在局部變量中。
  • 遍歷任何種類的集合時,也將長度存儲在局部變量中。

新代碼:

<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>

參考文獻:

如果我在這里錯了,請糾正我,但是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 += "<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);
}

這會將標記附加到您的軀干,然后完成堆棧的該部分。 我很確定“行”的每次迭代仍將保留在內存中; 但是,標記字符串等應最終釋放。

再次...已經有一段時間了,因為我一直在這個低水平上查看SetTimeout,所以我在這里可能是完全錯誤的。 無論如何,這不會消除泄漏,只會降低增長率。 這取決於正在使用的JavaScript引擎的垃圾收集器如何處理SetTimeout循環(如您在此處所述)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM