2009-09-21 28 views
6

Tôi có một trang web bị rò rỉ bộ nhớ trong cả IE8 và Firefox; việc sử dụng bộ nhớ được hiển thị trong Windows Process Explorer chỉ tiếp tục tăng theo thời gian.Rò rỉ bộ nhớ liên quan đến các yêu cầu jQuery Ajax

Trang sau yêu cầu url "unplanned.json", là tệp tĩnh không bao giờ thay đổi (mặc dù tôi đặt tiêu đề HTTP Cache-control HTTP thành no-cache để đảm bảo rằng yêu cầu Ajax luôn đi qua). Khi nó nhận được kết quả, nó sẽ xóa một bảng HTML, các vòng lặp trên mảng json mà nó lấy lại từ máy chủ và tự động thêm một hàng vào một bảng HTML cho mỗi mục nhập trong mảng. Sau đó, nó chờ 2 giây và lặp lại quá trình này.

Dưới đây là toàn bộ trang web:

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

Nếu nó giúp, đây là một ví dụ về json tôi gửi lại (đó là mảng này chính xác wuith hàng ngàn mục thay vì chỉ một):

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

EDIT: tôi đã chấp nhận câu trả lời cực kỳ hữu ích Toran, nhưng tôi cảm thấy tôi nên đăng một số mã bổ sung, vì removefromdom jQuery plugin của ông có một số hạn chế:

  • Nó chỉ xóa từng phần tử. Vì vậy, bạn không thể cho nó một truy vấn như `$ (" # nội dung tbody tr ")` và mong đợi nó để loại bỏ tất cả các yếu tố bạn đã chỉ định.
  • Bất kỳ yếu tố nào bạn xóa cùng với phần tử phải có thuộc tính `id`. Vì vậy, nếu tôi muốn loại bỏ `tbody` của tôi, sau đó tôi phải gán một` id` vào thẻ `tbody` của tôi hoặc nếu không nó sẽ đưa ra một lỗi.
  • Nó tự xóa phần tử và tất cả các phần tử của nó, vì vậy nếu bạn chỉ muốn xóa phần tử đó thì bạn sẽ phải tạo lại phần tử đó (hoặc sửa đổi plugin thành trống thay vì xóa).

Vì vậy, đây là trang của tôi ở trên được sửa đổi để sử dụng plugin của Toran. Để đơn giản, tôi không áp dụng bất kỳ lời khuyên hiệu suất chung nào offered by Peter. Dưới đây là trang mà bây giờ không có rò rỉ bộ nhớ còn:

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

THÊM EDIT: Tôi sẽ để lại câu hỏi của tôi không thay đổi, mặc dù nó có giá trị lưu ý rằng bộ nhớ bị rò rỉ này không có gì để làm với Ajax. Trong thực tế, đoạn code sau sẽ rò rỉ bộ nhớ chỉ giống nhau và được chỉ là một cách dễ dàng giải quyết với removefromdom jQuery plugin Toran của:

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); 
+0

Câu hỏi ngớ ngẩn, nhưng vì nó phụ thêm vào phần thân trên mỗi lần tìm nạp, làm thế nào nó có thể sử dụng cùng một lượng bộ nhớ? –

+0

Nó làm trống ra tbody trước khi phụ thêm vào nó. Tôi hy vọng những hàng trống sẽ bị thu gom rác, mặc dù ai đó chắc chắn sẽ cho tôi biết nếu tôi nhầm lẫn về điều này. –

+1

Trong IE6/7/8 bạn không thể thực sự loại bỏ một phần tử DOM được tạo động mà không làm việc lạ xung quanh để null bên trongHTML (set innerHTML = "") –

Trả lời

11

Tôi không chắc chắn tại sao firefox không hài lòng với điều này nhưng tôi có thể nói từ kinh nghiệm rằng trong IE6/7/8 bạn phải đặt innerHTML = ""; trên đối tượng bạn muốn xóa khỏi DOM. (nếu bạn đã tạo tự động phần tử DOM này)

$("#content tbody").empty(); có thể không phát hành các phần tử DOM được tạo động này.

Thay vào đó hãy thử một cái gì đó như dưới đây (đây là một plugin jQuery tôi đã viết để giải quyết vấn đề).

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 = ""; 
}; 

Bạn sẽ gọi đây là như vậy: $("#content").removefromdom();

Vấn đề duy nhất ở đây là bạn cần phải tái tạo bảng của bạn mỗi khi bạn muốn xây dựng nó.

Ngoài ra, nếu điều này giải quyết được sự cố của bạn trong IE, bạn có thể đọc thêm về điều này trong blog post mà tôi đã viết vào đầu năm nay khi tôi gặp phải vấn đề tương tự.

Chỉnh sửa Tôi đã cập nhật plugin ở trên là 95% JavaScript miễn phí ngay bây giờ để sử dụng jQuery nhiều hơn phiên bản trước. Bạn sẽ vẫn nhận thấy rằng tôi phải sử dụng innerHTML vì hàm jQuery html (""); không hoạt động tương tự cho IE6/7/8

+0

Cảm ơn rất nhiều, điều này đã khắc phục được sự cố của tôi. Tôi đã chỉnh sửa câu hỏi của mình để chỉ ra một số hạn chế của plugin này và đã đăng mã hiện đang làm việc và không bị rò rỉ để hiển thị cho mọi người cách áp dụng plugin của bạn cho trang của tôi. –

4

Tôi không chắc chắn về rò rỉ, nhưng chức năng resetTable() của bạn là rất không hiệu quả. Trước tiên, hãy thử khắc phục các sự cố đó và xem nơi bạn kết thúc.

  • Không thêm vào DOM trong vòng lặp. Nếu bạn phải thực hiện thao tác DOM, sau đó nối thêm vào một đoạn tài liệu và sau đó di chuyển phân đoạn đó đến DOM.
  • Tuy nhiên, innerHTML nhanh hơn thao tác DOM, vì vậy hãy sử dụng nếu bạn có thể.
  • Cửa hàng jQuery đặt thành các biến cục bộ - không cần phải chạy lại bộ chọn mỗi lần.
  • Cũng lưu trữ các tham chiếu lặp lại trong biến cục bộ.
  • Khi lặp qua bộ sưu tập thuộc bất kỳ loại nào, hãy lưu trữ độ dài trong biến cục bộ.

Code mới:

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

Tài liệu tham khảo:

+0

Đó là một số gợi ý tuyệt vời, nhưng tiếc là tôi vẫn thấy bộ nhớ bị rò rỉ khi chạy mã của bạn. Mọi thứ đều giống y chang ngoại trừ việc tôi đã thay thế "test.php" bằng "unplanned.json" - tôi cho rằng bạn đã tạo một trang test.php để tự mình thử nghiệm, điều này thực sự tuyệt vời và hữu ích. Vì vậy, cảm ơn cho đầu vào và tôi chắc chắn sẽ sử dụng một số kỹ thuật trong tương lai, nhưng nếu bạn có bất kỳ ý tưởng khác về những gì có thể gây ra việc sử dụng bộ nhớ này, thì tôi rất muốn nghe chúng. –

+1

Tôi đã làm. Tôi sẽ suy nghĩ về vấn đề này một số chi tiết và xem tôi có thể nghĩ ra được điều gì không. Kích thước của dữ liệu bạn đang quay trở lại AJAX là bao nhiêu? –

0

Xin vui lòng sửa cho tôi nếu tôi sai ở đây nhưng không setTimeout (fn) ngăn chặn việc phát hành ca không gian bộ nhớ của ller? Vì vậy, tất cả các biến/bộ nhớ được cấp phát trong phương thức resetTable (hàng) sẽ vẫn được cấp phát cho đến khi vòng lặp kết thúc?

Nếu trường hợp đó xảy ra, việc đẩy chuỗi xây dựng và chắp thêm logic vào một phương pháp khác có thể tốt hơn một chút vì các đối tượng đó sẽ được giải phóng sau mỗi lần gọi và chỉ bộ nhớ của giá trị trả về (trong trường hợp này là đánh dấu chuỗi hoặc không có gì nếu phương thức mới đã làm appendTo()) sẽ vẫn còn trong bộ nhớ.

Về bản chất:

Gọi ban đầu để kickoff

-> Calls resettable()

-> -> setTimeout cuộc gọi kickoff lại

-> -> -> Calls resettable () một lần nữa

-> -> -> -> Tiếp tục đến Infinite

Nếu mã không bao giờ thực sự giải quyết, cây sẽ tiếp tục phát triển.

Một cách khác để giải phóng một số bộ nhớ dựa trên này sẽ như thế nào vào mã bên dưới:

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); 
} 

này sẽ nối đánh dấu để tbody bạn và sau đó hoàn thành một phần của ngăn xếp. Tôi khá chắc chắn rằng mỗi lần lặp lại của "hàng" sẽ vẫn còn trong bộ nhớ; tuy nhiên, chuỗi đánh dấu và chuỗi đó sẽ miễn phí cuối cùng.

Một lần nữa ... đã lâu rồi tôi mới nhìn vào SetTimeout ở mức độ thấp này nên tôi có thể hoàn toàn sai ở đây. Trong anycase, điều này sẽ không loại bỏ rò rỉ và chỉ có thể làm giảm tốc độ tăng trưởng. Nó phụ thuộc vào cách trình thu gom rác của các công cụ JavaScript đang sử dụng xử lý các vòng lặp SetTimeout như bạn có ở đây.

+0

Cảm ơn đề xuất - Tôi nhận được trang của mình hoạt động nhờ đề xuất của Toran ở trên, nhưng đôi khi trong tương lai tôi sẽ xem ý tưởng của bạn và xem họ có hoạt động không. Nếu họ có thể sẽ chấp nhận đề xuất của bạn thay vì nó đơn giản hơn nhiều so với plugin jQuery mà Toran đã viết, mặc dù tôi đoán cách tiếp cận của anh ấy có lẽ là cách duy nhất để làm cho trang của tôi không bị rò rỉ bộ nhớ. –

Các vấn đề liên quan