Tìm rò rỉ bộ nhớ JavaScript với Chrome


163

Tôi đã tạo một trường hợp thử nghiệm rất đơn giản để tạo chế độ xem Xương sống, gắn trình xử lý vào một sự kiện và khởi tạo một lớp do người dùng định nghĩa. Tôi tin rằng bằng cách nhấp vào nút "Xóa" trong mẫu này, mọi thứ sẽ được dọn sạch và sẽ không có rò rỉ bộ nhớ.

Một jsfiddle cho mã ở đây: http://jsfiddle.net/4QhR2/

// scope everything to a function
function main() {

    function MyWrapper() {
        this.element = null;
    }
    MyWrapper.prototype.set = function(elem) {
        this.element = elem;
    }
    MyWrapper.prototype.get = function() {
        return this.element;
    }

    var MyView = Backbone.View.extend({
        tagName : "div",
        id : "view",
        events : {
            "click #button" : "onButton",
        },    
        initialize : function(options) {        
            // done for demo purposes only, should be using templates
            this.html_text = "<input type='text' id='textbox' /><button id='button'>Remove</button>";        
            this.listenTo(this,"all",function(){console.log("Event: "+arguments[0]);});
        },
        render : function() {        
            this.$el.html(this.html_text);

            this.wrapper = new MyWrapper();
            this.wrapper.set(this.$("#textbox"));
            this.wrapper.get().val("placeholder");

            return this;
        },
        onButton : function() {
            // assume this gets .remove() called on subviews (if they existed)
            this.trigger("cleanup");
            this.remove();
        }
    });

    var view = new MyView();
    $("#content").append(view.render().el);
}

main();

Tuy nhiên, tôi không rõ cách sử dụng trình tạo hồ sơ của Google Chrome để xác minh rằng trên thực tế, đây là trường hợp. Có một số thứ hiển thị trên ảnh chụp nhanh hồ sơ heap, và tôi không biết làm thế nào để giải mã những gì tốt / xấu. Các hướng dẫn mà tôi đã thấy trên đó cho đến nay chỉ là bảo tôi "sử dụng trình tạo ảnh chụp nhanh" hoặc đưa cho tôi bản tuyên ngôn cực kỳ chi tiết về cách thức hoạt động của toàn bộ trình lược tả. Có thể chỉ sử dụng profiler như một công cụ, hoặc tôi thực sự phải hiểu làm thế nào toàn bộ được thiết kế?

EDIT: Hướng dẫn như thế này:

Sửa lỗi rò rỉ bộ nhớ Gmail

Sử dụng DevTools

Là đại diện của một số vật liệu mạnh hơn ngoài kia, từ những gì tôi đã thấy. Tuy nhiên, ngoài việc giới thiệu khái niệm về 3 Kỹ thuật chụp nhanh , tôi thấy họ cung cấp rất ít về kiến ​​thức thực tế (cho một người mới bắt đầu như tôi). Hướng dẫn 'Sử dụng DevTools' không hoạt động thông qua một ví dụ thực tế, vì vậy mô tả khái niệm mơ hồ và chung chung của nó về những điều không quá hữu ích. Đối với ví dụ 'Gmail':

Vì vậy, bạn tìm thấy một rò rỉ. Giờ thì sao?

  • Kiểm tra đường dẫn giữ lại của các đối tượng bị rò rỉ ở nửa dưới của bảng Cấu hình

  • Nếu trang phân bổ không thể dễ dàng được suy ra (tức là người nghe sự kiện):

  • Công cụ xây dựng của đối tượng lưu giữ thông qua bảng điều khiển JS để lưu dấu vết ngăn xếp để phân bổ

  • Sử dụng đóng cửa? Cho phép cờ hiện có thích hợp (ví dụ: goog.events.Listener.ENABLE_MONITORING) để đặt thuộc tính CreationStack trong khi xây dựng

Tôi thấy mình bối rối hơn sau khi đọc nó, không ít. Và, một lần nữa, nó chỉ bảo tôi làm mọi thứ chứ không phải làm thế nào . Từ quan điểm của tôi, tất cả các thông tin ngoài kia là quá mơ hồ hoặc sẽ chỉ có ý nghĩa với một người đã hiểu quá trình.

Một số vấn đề cụ thể hơn đã được nêu ra trong câu trả lời của @Jonathan Naguin bên dưới.


2
Tôi không biết gì về việc kiểm tra sử dụng bộ nhớ trong các trình duyệt, nhưng trong trường hợp bạn chưa thấy nó, bài viết của Addy Osman về trình kiểm tra web Chrome có thể hữu ích.
Paul D. Chờ đợi

1
Cảm ơn lời đề nghị, Paul. Tuy nhiên, khi tôi chụp một ảnh chụp nhanh trước khi nhấp vào xóa, rồi một ảnh chụp khác sau khi được nhấp, sau đó chọn 'các đối tượng được phân bổ giữa các ảnh chụp nhanh 1 và 2' (như được đề xuất trong bài viết của anh ấy) vẫn còn hơn 2000 đối tượng. Chẳng hạn, có 4 mục 'HTMLButtonEuity', không có ý nghĩa gì với tôi. Thực sự, tôi không biết chuyện gì đang xảy ra.
EleventyOne

3
doh, điều đó không có vẻ đặc biệt hữu ích. Có thể là với một ngôn ngữ được thu thập rác như JavaScript, bạn không thực sự có ý định xác minh những gì bạn đang làm với bộ nhớ ở mức độ chi tiết như bài kiểm tra của bạn. Cách tốt hơn để kiểm tra rò rỉ bộ nhớ có thể là gọi main10.000 lần thay vì một lần và xem cuối cùng bạn có sử dụng nhiều bộ nhớ hơn không.
Paul D. Chờ đợi

3
@ PaulD.Waite Vâng, có lẽ. Nhưng dường như tôi vẫn cần phân tích mức độ chi tiết để xác định chính xác vấn đề là gì, thay vì chỉ có thể nói (hoặc không nói): "Được rồi, có vấn đề về bộ nhớ ở đây". Và tôi có ấn tượng rằng tôi sẽ có thể sử dụng trình hồ sơ của họ ở mức độ chi tiết như vậy ... Tôi chỉ không chắc là thế nào :(
EleventyOne

Câu trả lời:


205

Một quy trình công việc tốt để tìm rò rỉ bộ nhớ là ba kỹ thuật chụp nhanh , lần đầu tiên được sử dụng bởi Loreena Lee và nhóm Gmail để giải quyết một số vấn đề về bộ nhớ của họ. Các bước nói chung là:

  • Chụp ảnh đống.
  • Làm công cụ.
  • Chụp ảnh heap khác.
  • Lặp lại những thứ tương tự.
  • Chụp ảnh heap khác.
  • Các đối tượng lọc được phân bổ giữa Ảnh chụp nhanh 1 và 2 trong chế độ xem "Tóm tắt" của Ảnh chụp nhanh 3.

Ví dụ của bạn, tôi đã điều chỉnh mã để hiển thị quá trình này (bạn có thể tìm thấy nó ở đây ) trì hoãn việc tạo Chế độ xem xương sống cho đến khi sự kiện nhấp của nút Bắt đầu. Hiện nay:

  • Chạy HTML (được lưu cục bộ bằng cách sử dụng địa chỉ này ) và chụp nhanh.
  • Nhấp vào Bắt đầu để tạo chế độ xem.
  • Chụp ảnh nhanh khác.
  • Nhấp vào loại bỏ.
  • Chụp ảnh nhanh khác.
  • Các đối tượng lọc được phân bổ giữa Ảnh chụp nhanh 1 và 2 trong chế độ xem "Tóm tắt" của Ảnh chụp nhanh 3.

Bây giờ bạn đã sẵn sàng để tìm rò rỉ bộ nhớ!

Bạn sẽ nhận thấy các nút của một vài màu sắc khác nhau. Các nút đỏ không có các tham chiếu trực tiếp từ Javascript đến chúng, nhưng vẫn còn sống vì chúng là một phần của cây DOM tách rời. Có thể có một nút trong cây được tham chiếu từ Javascript (có thể là một bao đóng hoặc biến) nhưng ngẫu nhiên ngăn toàn bộ cây DOM không bị thu gom rác.

nhập mô tả hình ảnh ở đây

Các nút màu vàng tuy nhiên có tham chiếu trực tiếp từ Javascript. Tìm kiếm các nút màu vàng trong cùng một cây DOM tách rời để định vị các tham chiếu từ Javascript của bạn. Cần có một chuỗi các thuộc tính dẫn từ cửa sổ DOM đến phần tử.

Cụ thể, bạn có thể thấy phần tử Div HTML được đánh dấu màu đỏ. Nếu bạn mở rộng phần tử, bạn sẽ thấy được tham chiếu bởi chức năng "bộ đệm".

nhập mô tả hình ảnh ở đây

Chọn hàng và trong bảng điều khiển của bạn gõ $ 0, bạn sẽ thấy chức năng và vị trí thực tế:

>$0
function cache( key, value ) {
        // Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
        if ( keys.push( key += " " ) > Expr.cacheLength ) {
            // Only keep the most recent entries
            delete cache[ keys.shift() ];
        }
        return (cache[ key ] = value);
    }                                                     jquery-2.0.2.js:1166

Đây là nơi yếu tố của bạn đang được tham chiếu. Đáng tiếc là bạn không thể làm được gì nhiều, đó là một cơ chế nội bộ từ jQuery. Nhưng, chỉ với mục đích thử nghiệm, hãy chuyển hàm và thay đổi phương thức thành:

function cache( key, value ) {
    return value;
}

Bây giờ nếu bạn lặp lại quá trình, bạn sẽ không thấy bất kỳ nút đỏ nào :)

Tài liệu:


8
Tôi đánh giá cao nỗ lực của bạn. Thật vậy, ba kỹ thuật chụp nhanh thường được đề cập trong hướng dẫn. Thật không may, các chi tiết thường bị bỏ qua. Ví dụ, tôi đánh giá cao việc giới thiệu $0chức năng trong bảng điều khiển, điều này mới đối với tôi - tất nhiên, tôi không biết điều gì đang làm hoặc cách bạn biết sử dụng nó ( $1dường như vô dụng trong khi $2dường như làm điều tương tự). Thứ hai, làm thế nào bạn biết để làm nổi bật hàng #button in function cache()và không phải bất kỳ hàng chục hàng khác? Cuối cùng, có nút đỏ NodeListHTMLInputElementquá, nhưng tôi không thể hình dung chúng ra.
EleventyOne

7
Làm thế nào bạn biết rằng cachehàng chứa thông tin trong khi những người khác thì không? Có rất nhiều chi nhánh có khoảng cách thấp hơn cache. Và tôi không chắc làm thế nào bạn biết rằng đó HTMLInputElementlà một đứa trẻ HTMLDivElement. Tôi thấy nó được tham chiếu bên trong nó ("bản địa trong HTMLDivEuity"), nhưng nó cũng tham chiếu chính nó và hai HTMLButtonElements, điều này không có ý nghĩa với tôi. Tôi chắc chắn đánh giá cao bạn xác định câu trả lời cho ví dụ này, nhưng tôi thực sự sẽ không biết làm thế nào để khái quát nó cho các vấn đề khác.
EleventyOne

2
Điều đó thật lạ, tôi đã sử dụng ví dụ của bạn và tôi đã nhận được một kết quả khác với bạn (từ ảnh chụp màn hình của bạn). Tuy nhiên, tôi đánh giá rất cao tất cả sự giúp đỡ của bạn. Tôi nghĩ bây giờ tôi đã có đủ, và khi tôi có một ví dụ thực tế mà tôi cần sự giúp đỡ cụ thể, tôi sẽ tạo một câu hỏi mới ở đây. Cảm ơn một lần nữa.
EleventyOne

2
Có thể tìm thấy giải thích về $ 0 tại đây: developer.chrome.com/devtools/docs/commandline-api#0-4
Sukrit Gupta

4
Filter objects allocated between Snapshots 1 and 2 in Snapshot 3's "Summary" view.nghĩa là gì?
K - Độc tính trong SO đang tăng lên.

8

Đây là một mẹo về cấu hình bộ nhớ của jsfiddle: Sử dụng URL sau để tách kết quả jsfiddle của bạn, nó sẽ xóa tất cả khung jsfiddle và chỉ tải kết quả của bạn.

http://jsfiddle.net/4QhR2/show/

Tôi không bao giờ có thể tìm ra cách sử dụng Timeline và Profiler để theo dõi rò rỉ bộ nhớ, cho đến khi tôi đọc tài liệu sau đây. Sau khi đọc phần có tên 'Trình theo dõi phân bổ đối tượng', tôi đã có thể sử dụng công cụ 'Record Heap Allocations' và theo dõi một số nút DOM tách rời.

Tôi đã khắc phục sự cố bằng cách chuyển từ liên kết sự kiện jQuery sang sử dụng ủy quyền sự kiện Backbone. Theo hiểu biết của tôi, các phiên bản mới hơn của Backbone sẽ tự động hủy liên kết các sự kiện cho bạn nếu bạn gọi View.remove(). Thực hiện một số bản demo, chúng được thiết lập với rò rỉ bộ nhớ để bạn xác định. Vui lòng đặt câu hỏi ở đây nếu bạn vẫn không nhận được nó sau khi nghiên cứu tài liệu này.

https://developers.google.com/chrome-developer-tools/docs/javascript-memory-profiling


6

Về cơ bản, bạn cần nhìn vào số lượng đối tượng bên trong ảnh chụp heap của bạn. Nếu số lượng đối tượng tăng lên giữa hai ảnh chụp nhanh và bạn đã xử lý các đối tượng thì bạn bị rò rỉ bộ nhớ. Lời khuyên của tôi là tìm kiếm các trình xử lý sự kiện trong mã của bạn mà không bị tách ra.


3
Chẳng hạn, nếu tôi nhìn vào một ảnh chụp nhanh của jsfiddle, trước khi tôi nhấp vào 'Xóa', có hơn 100.000 đối tượng hiện diện. Tôi sẽ tìm các đối tượng mà mã jsfiddle của tôi thực sự tạo ở đâu? Tôi nghĩ rằng nó Window/http://jsfiddle.net/4QhR2/showcó thể hữu ích, nhưng nó chỉ là chức năng vô tận. Tôi không biết chuyện gì đang xảy ra ở đó.
EleventyOne

@EleventyOne: Tôi sẽ không sử dụng jsFiddle. Tại sao không chỉ tạo một tệp trên máy tính của riêng bạn để thử nghiệm?
Bầu trời xanh

1
@BlueSkies Tôi đã tạo một jsfiddle để mọi người ở đây có thể làm việc từ cùng một cơ sở mã. Tuy nhiên, khi tôi tạo một tệp trên máy tính của mình để thử nghiệm, vẫn còn hơn 50.000 đối tượng có trong ảnh chụp heap.
EleventyOne

@EleventyOne Một ảnh chụp nhanh heap không cung cấp cho bạn ý tưởng về việc bạn có bị rò rỉ bộ nhớ hay không. Bạn cần ít nhất hai.
Konstantin Dinev

2
Thật. Tôi đã nhấn mạnh rằng thật khó để biết phải tìm gì khi có hàng ngàn vật thể hiện diện.
EleventyOne


3

Bạn cũng có thể nhìn vào tab Dòng thời gian trong các công cụ dành cho nhà phát triển. Ghi lại việc sử dụng ứng dụng của bạn và theo dõi số lượng người nghe DOM Node và Event.

Nếu biểu đồ bộ nhớ thực sự sẽ chỉ ra rò rỉ bộ nhớ, thì bạn có thể sử dụng trình lược tả để tìm ra những gì bị rò rỉ.



2

Tôi thứ hai lời khuyên để chụp ảnh nhanh, chúng rất tuyệt vời trong việc phát hiện rò rỉ bộ nhớ, chrome thực hiện công việc chụp nhanh.

Trong dự án nghiên cứu của tôi để lấy bằng, tôi đã xây dựng một ứng dụng web tương tác phải tạo ra nhiều dữ liệu được tích hợp trong 'lớp', nhiều lớp trong số này sẽ bị 'xóa' trong UI nhưng vì một số lý do, bộ nhớ không bị xử lý, sử dụng công cụ chụp nhanh Tôi có thể xác định rằng JQuery đã giữ một tham chiếu trên đối tượng (nguồn là khi tôi đang cố gắng kích hoạt một .load()sự kiện giữ tham chiếu mặc dù đi ra khỏi phạm vi). Có sẵn thông tin này trong tay đã lưu dự án của tôi, đây là một công cụ rất hữu ích khi bạn đang sử dụng thư viện của người khác và bạn gặp phải vấn đề kéo dài các tài liệu tham khảo ngăn chặn GC thực hiện công việc của mình.

EDIT: Cũng rất hữu ích khi lên kế hoạch trước những hành động bạn sẽ thực hiện để giảm thiểu thời gian chụp nhanh, đưa ra giả thuyết điều gì có thể gây ra sự cố và kiểm tra từng kịch bản, thực hiện các ảnh chụp nhanh trước và sau.


0

Một số lưu ý quan trọng liên quan đến việc xác định rò rỉ bộ nhớ bằng các công cụ Chrome Developer:

1) Bản thân Chrome có rò rỉ bộ nhớ cho các yếu tố nhất định như trường mật khẩu và số. https://bugs.chromium.org/p/chromium/issues/detail?id=967438 . Tránh sử dụng chúng trong khi gỡ lỗi vì chúng sẽ xử lý ảnh chụp nhanh heap của bạn khi tìm kiếm các phần tử tách rời.

2) Tránh đăng nhập bất cứ thứ gì vào bảng điều khiển trình duyệt. Chrome sẽ không thu gom rác các đối tượng được ghi vào bảng điều khiển, do đó ảnh hưởng đến kết quả của bạn. Bạn có thể chặn đầu ra bằng cách đặt đoạn mã sau vào đầu tập lệnh / trang của bạn:

console.log = function() {};
console.warn = console.log;
console.error = console.log;

3) Sử dụng ảnh chụp nhanh heap và tìm kiếm "tách" để xác định các phần tử DOM tách rời. Bằng cách di chuột vào các đối tượng, bạn có quyền truy cập vào tất cả các thuộc tính bao gồm idbên ngoàiHTML có thể giúp xác định từng thành phần. Ảnh chụp màn hình của Ảnh chụp nhanh Heap với chi tiết về phần tử DOM tách rời Nếu các yếu tố tách rời vẫn quá chung chung để nhận ra, hãy gán cho chúng ID duy nhất bằng bảng điều khiển trình duyệt trước khi chạy thử nghiệm của bạn, ví dụ:

var divs = document.querySelectorAll("div");
for (var i = 0 ; i < divs.length ; i++)
{
    divs[i].id = divs[i].id || "AutoId_" + i;
}
divs = null; // Free memory

Bây giờ, khi bạn xác định một phần tử tách rời với, hãy nói id = "AutoId_49", tải lại trang của bạn, thực hiện đoạn mã ở trên một lần nữa và tìm phần tử với id = "AutoId_49" bằng cách sử dụng trình kiểm tra DOM hoặc document.querySelector (..) . Đương nhiên điều này chỉ hoạt động nếu nội dung trang của bạn có thể dự đoán được.

Cách tôi chạy thử nghiệm để xác định rò rỉ bộ nhớ

1) Tải trang (với đầu ra giao diện điều khiển bị chặn!)

2) Làm những thứ trên trang có thể dẫn đến rò rỉ bộ nhớ

3) Sử dụng Công cụ dành cho nhà phát triển để chụp ảnh nhanh và tìm kiếm "tách"

4) Di chuột để xác định chúng từ các thuộc tính id hoặc bên ngoài của chúng


Ngoài ra, luôn luôn là một ý tưởng tốt để vô hiệu hóa thu nhỏ / làm xấu đi vì nó làm cho việc gỡ lỗi trong trình duyệt trở nên khó khăn hơn.
Jimmy Thomsen
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.