Javascript có bộ thu gom rác không?

Trong bài viết này, chúng ta sẽ khám phá các loại rò rỉ bộ nhớ phổ biến trong mã JavaScript phía máy khách. Chúng ta cũng sẽ tìm hiểu cách sử dụng Công cụ phát triển Chrome để tìm chúng. đọc tiếp


Giới thiệu

Rò rỉ bộ nhớ là vấn đề mà mọi nhà phát triển cuối cùng phải đối mặt. Ngay cả khi làm việc với các ngôn ngữ quản lý bộ nhớ, vẫn có trường hợp bộ nhớ có thể bị rò rỉ. Rò rỉ là nguyên nhân của cả lớp vấn đề. chậm, sự cố, độ trễ cao và thậm chí là sự cố với các ứng dụng khác

Rò rỉ bộ nhớ là gì?

Về bản chất, rò rỉ bộ nhớ có thể được định nghĩa là bộ nhớ không được ứng dụng yêu cầu nữa mà vì lý do nào đó không được trả về hệ điều hành hoặc nhóm bộ nhớ trống. Các ngôn ngữ lập trình ủng hộ các cách quản lý bộ nhớ khác nhau. Những cách này có thể làm giảm cơ hội rò rỉ bộ nhớ. Tuy nhiên, một phần bộ nhớ nào đó có được sử dụng hay không thực sự là một vấn đề không thể giải quyết được. Nói cách khác, chỉ các nhà phát triển mới có thể làm rõ liệu một phần bộ nhớ có thể được trả lại cho hệ điều hành hay không. Một số ngôn ngữ lập trình cung cấp các tính năng giúp nhà phát triển thực hiện việc này. Những người khác mong đợi các nhà phát triển hoàn toàn rõ ràng về thời điểm một phần bộ nhớ không được sử dụng. Wikipedia có các bài viết hay về quản lý bộ nhớ thủ công và tự động

Quản lý bộ nhớ trong JavaScript

JavaScript là một trong những ngôn ngữ được gọi là thu gom rác. Các ngôn ngữ thu gom rác giúp các nhà phát triển quản lý bộ nhớ bằng cách kiểm tra định kỳ những phần bộ nhớ được phân bổ trước đó vẫn có thể được "tiếp cận" từ các phần khác của ứng dụng. Nói cách khác, các ngôn ngữ được thu gom rác làm giảm vấn đề quản lý bộ nhớ từ "bộ nhớ nào vẫn được yêu cầu?" . Sự khác biệt là tinh tế, nhưng quan trọng. trong khi chỉ nhà phát triển mới biết liệu một phần bộ nhớ được phân bổ có được yêu cầu trong tương lai hay không, thì bộ nhớ không thể truy cập có thể được xác định bằng thuật toán và được đánh dấu để quay lại HĐH

Các ngôn ngữ không thu gom rác thường sử dụng các kỹ thuật khác để quản lý bộ nhớ. quản lý rõ ràng, trong đó nhà phát triển nói rõ ràng với trình biên dịch khi không cần một phần bộ nhớ; . Những kỹ thuật này đi kèm với sự đánh đổi của chính chúng (và các nguyên nhân tiềm ẩn dẫn đến rò rỉ)

Rò rỉ trong JavaScript

Nguyên nhân chính dẫn đến rò rỉ trong các ngôn ngữ được thu gom rác là các tham chiếu không mong muốn. Để hiểu các tham chiếu không mong muốn là gì, trước tiên chúng ta cần hiểu cách trình thu gom rác xác định xem có thể truy cập một phần bộ nhớ hay không

"Nguyên nhân chính gây rò rỉ trong các ngôn ngữ được thu gom rác là các tham chiếu không mong muốn. "

Javascript có bộ thu gom rác không?

Tweet cái này đi

đánh dấu và quét

Hầu hết các trình thu gom rác sử dụng một thuật toán được gọi là đánh dấu và quét. Thuật toán bao gồm các bước sau

  1. Trình thu gom rác xây dựng danh sách "gốc". Gốc thường là các biến toàn cục mà một tham chiếu được giữ trong mã. Trong JavaScript, đối tượng "cửa sổ" là một ví dụ về biến toàn cục có thể đóng vai trò là gốc. Đối tượng cửa sổ luôn hiện diện, vì vậy bộ thu gom rác có thể coi nó và tất cả các phần tử con của nó luôn hiện diện (i. e. không rác)
  2. Tất cả các rễ được kiểm tra và đánh dấu là hoạt động (i. e. không rác). Tất cả trẻ em cũng được kiểm tra đệ quy. Mọi thứ có thể đạt được từ gốc không được coi là rác
  3. Tất cả các phần bộ nhớ không được đánh dấu là hoạt động hiện có thể được coi là rác. Trình thu thập hiện có thể giải phóng bộ nhớ đó và trả lại cho hệ điều hành

Trình thu gom rác hiện đại cải thiện thuật toán này theo những cách khác nhau, nhưng bản chất là giống nhau. các phần bộ nhớ có thể truy cập được đánh dấu như vậy và phần còn lại được coi là rác

Các tham chiếu không mong muốn là các tham chiếu đến các phần bộ nhớ mà nhà phát triển biết rằng họ sẽ không cần nữa nhưng vì lý do nào đó được giữ bên trong cây của một gốc đang hoạt động. Trong ngữ cảnh của JavaScript, các tham chiếu không mong muốn là các biến được lưu giữ ở đâu đó trong mã sẽ không được sử dụng nữa và trỏ đến một phần bộ nhớ có thể được giải phóng. Một số người cho rằng đây là lỗi của nhà phát triển

Vì vậy, để hiểu đâu là lỗ hổng phổ biến nhất trong JavaScript, chúng ta cần biết cách tham chiếu nào thường bị lãng quên

Ba loại rò rỉ JavaScript phổ biến

1. Các biến toàn cầu ngẫu nhiên

Một trong những mục tiêu đằng sau JavaScript là phát triển một ngôn ngữ trông giống như Java nhưng đủ dễ sử dụng cho người mới bắt đầu. Một trong những cách mà JavaScript được cho phép là cách nó xử lý các biến không được khai báo. một tham chiếu đến một biến không được khai báo sẽ tạo một biến mới bên trong đối tượng toàn cầu. Trong trường hợp trình duyệt, đối tượng toàn cục là

function foo() {
    this.variable = "potential accidental global";
}

// Foo called on its own, this points to the global object (window)
// rather than being undefined.
foo();
0. Nói cách khác

function foo(arg) {
    bar = "this is a hidden global variable";
}

Là trong thực tế

function foo(arg) {
    window.bar = "this is an explicit global variable";
}

Nếu

function foo(arg) {
    window.bar = "this is an explicit global variable";
}
0 được cho là chỉ giữ tham chiếu đến một biến trong phạm vi của hàm
function foo(arg) {
    window.bar = "this is an explicit global variable";
}
1 và bạn quên sử dụng
function foo(arg) {
    window.bar = "this is an explicit global variable";
}
2 để khai báo nó, thì một biến toàn cục không mong muốn sẽ được tạo. Trong ví dụ này, rò rỉ một chuỗi đơn giản sẽ không gây hại nhiều, nhưng nó chắc chắn có thể tồi tệ hơn

Một cách khác mà một biến toàn cục ngẫu nhiên có thể được tạo ra là thông qua

function foo(arg) {
    window.bar = "this is an explicit global variable";
}
3

function foo() {
    this.variable = "potential accidental global";
}

// Foo called on its own, this points to the global object (window)
// rather than being undefined.
foo();

Để tránh những lỗi này xảy ra, hãy thêm

function foo(arg) {
    window.bar = "this is an explicit global variable";
}
4 vào đầu tệp JavaScript của bạn. Điều này cho phép chế độ phân tích cú pháp JavaScript chặt chẽ hơn để ngăn chặn các toàn cầu ngẫu nhiên

Một lưu ý về các biến toàn cầu

Mặc dù chúng ta nói về các biến toàn cầu không bị nghi ngờ, nhưng vẫn có trường hợp nhiều mã chứa đầy các biến toàn cầu rõ ràng. Theo định nghĩa, đây là những thứ không thể thu thập được (trừ khi bị vô hiệu hóa hoặc chỉ định lại). Đặc biệt, các biến toàn cục được sử dụng để lưu trữ và xử lý tạm thời một lượng lớn thông tin rất đáng quan tâm. Nếu bạn phải sử dụng một biến toàn cục để lưu trữ nhiều dữ liệu, hãy đảm bảo hủy nó hoặc chỉ định lại nó sau khi bạn hoàn thành nó. Một nguyên nhân phổ biến làm tăng mức tiêu thụ bộ nhớ liên quan đến toàn cầu là bộ đệm). Bộ nhớ cache lưu trữ dữ liệu được sử dụng nhiều lần. Để điều này có hiệu quả, bộ đệm phải có giới hạn trên cho kích thước của nó. Bộ nhớ cache phát triển không giới hạn có thể dẫn đến mức tiêu thụ bộ nhớ cao vì không thể thu thập nội dung của chúng

2. Quên hẹn giờ hoặc gọi lại

Việc sử dụng

function foo(arg) {
    window.bar = "this is an explicit global variable";
}
5 khá phổ biến trong JavaScript. Các thư viện khác cung cấp trình quan sát và các cơ sở khác nhận cuộc gọi lại. Hầu hết các thư viện này quan tâm đến việc làm cho bất kỳ tham chiếu nào đến cuộc gọi lại không thể truy cập được sau khi các phiên bản của chính chúng cũng không thể truy cập được. Tuy nhiên, trong trường hợp setInterval, mã như thế này khá phổ biến

var someResource = getData();
setInterval(function() {
    var node = document.getElementById('Node');
    if(node) {
        // Do stuff with node and someResource.
        node.innerHTML = JSON.stringify(someResource));
    }
}, 1000);

Ví dụ này minh họa những gì có thể xảy ra với bộ hẹn giờ lơ lửng. bộ hẹn giờ tham chiếu đến các nút hoặc dữ liệu không còn cần thiết. Đối tượng được đại diện bởi

function foo(arg) {
    window.bar = "this is an explicit global variable";
}
6 có thể bị xóa trong tương lai, làm cho toàn bộ khối bên trong trình xử lý khoảng thời gian trở nên không cần thiết. Tuy nhiên, trình xử lý, vì khoảng thời gian vẫn đang hoạt động, không thể được thu thập (khoảng thời gian cần phải dừng để điều đó xảy ra). Nếu không thể thu thập trình xử lý khoảng thời gian, thì các phần phụ thuộc của nó cũng không thể được thu thập. Điều đó có nghĩa là
function foo(arg) {
    window.bar = "this is an explicit global variable";
}
7, có lẽ lưu trữ dữ liệu khá lớn, cũng không thể được thu thập

Đối với trường hợp của người quan sát, điều quan trọng là phải thực hiện các cuộc gọi rõ ràng để xóa chúng khi chúng không còn cần thiết nữa (hoặc đối tượng được liên kết sắp không thể truy cập được). Trước đây, điều này từng đặc biệt quan trọng vì một số trình duyệt nhất định (Internet Explorer 6) không thể quản lý tốt các tham chiếu theo chu kỳ (xem bên dưới để biết thêm thông tin về điều đó). Ngày nay, hầu hết các trình duyệt có thể và sẽ thu thập trình xử lý trình quan sát sau khi đối tượng được quan sát không thể truy cập được, ngay cả khi trình xử lý không bị xóa rõ ràng. Tuy nhiên, vẫn nên loại bỏ rõ ràng những người quan sát này trước khi đối tượng được xử lý. Ví dụ

function foo(arg) {
    window.bar = "this is an explicit global variable";
}
2

Một lưu ý về người quan sát đối tượng và tham chiếu theo chu kỳ

Người quan sát và tham chiếu theo chu kỳ từng là nguyên nhân của các nhà phát triển JavaScript. Đây là trường hợp do lỗi (hoặc quyết định thiết kế) trong trình thu gom rác của Internet Explorer. Các phiên bản cũ của Internet Explorer không thể phát hiện các tham chiếu theo chu kỳ giữa các nút DOM và mã JavaScript. Đây là điển hình của một người quan sát, thường giữ một tham chiếu đến có thể quan sát được (như trong ví dụ trên). Nói cách khác, mỗi khi một người quan sát được thêm vào một nút trong Internet Explorer, nó sẽ dẫn đến rò rỉ. Đây là lý do các nhà phát triển bắt đầu loại bỏ các trình xử lý một cách rõ ràng trước các nút hoặc hủy tham chiếu bên trong các trình quan sát. Ngày nay, các trình duyệt hiện đại (bao gồm Internet Explorer và Microsoft Edge) sử dụng các thuật toán thu gom rác hiện đại có thể phát hiện các chu trình này và xử lý chúng một cách chính xác. Nói cách khác, không nhất thiết phải gọi

function foo(arg) {
    window.bar = "this is an explicit global variable";
}
8 trước khi làm cho một nút không thể truy cập được

Các khung và thư viện như jQuery sẽ loại bỏ các trình nghe trước khi xử lý một nút (khi sử dụng các API cụ thể của chúng cho điều đó). Điều này được xử lý nội bộ bởi các thư viện và đảm bảo rằng không có rò rỉ nào được tạo ra, ngay cả khi chạy dưới các trình duyệt có vấn đề như Internet Explorer cũ

3. Ngoài tham chiếu DOM

Đôi khi có thể hữu ích khi lưu trữ các nút DOM bên trong cấu trúc dữ liệu. Giả sử bạn muốn cập nhật nhanh nội dung của một số hàng trong bảng. Có thể hợp lý khi lưu trữ tham chiếu đến từng hàng DOM trong từ điển hoặc mảng. Khi điều này xảy ra, hai tham chiếu đến cùng một phần tử DOM được giữ lại. một trong cây DOM và một trong từ điển. Nếu tại một thời điểm nào đó trong tương lai, bạn quyết định xóa các hàng này, bạn cần đặt cả hai tham chiếu không thể truy cập được

function foo(arg) {
    window.bar = "this is an explicit global variable";
}
4

Một cân nhắc bổ sung cho điều này phải được thực hiện với các tham chiếu đến các nút bên trong hoặc nút lá bên trong cây DOM. Giả sử bạn giữ tham chiếu đến một ô cụ thể của bảng (thẻ

function foo(arg) {
    window.bar = "this is an explicit global variable";
}
9) trong mã JavaScript của mình. Tại một thời điểm nào đó trong tương lai, bạn quyết định xóa bảng khỏi DOM nhưng vẫn giữ tham chiếu đến ô đó. Theo trực giác, người ta có thể cho rằng GC sẽ thu thập mọi thứ trừ ô đó. Trong thực tế điều này sẽ không xảy ra. ô là một nút con của bảng đó và trẻ em giữ các tham chiếu đến cha mẹ của chúng. Nói cách khác, tham chiếu đến ô của bảng từ mã JavaScript khiến toàn bộ bảng nằm trong bộ nhớ. Cân nhắc điều này cẩn thận khi giữ tham chiếu đến các phần tử DOM

4. đóng cửa

Một khía cạnh quan trọng của phát triển JavaScript là bao đóng. các hàm ẩn danh nắm bắt các biến từ phạm vi cha. Các nhà phát triển của Meteor đã tìm thấy một trường hợp cụ thể trong đó do các chi tiết triển khai của thời gian chạy JavaScript, có thể rò rỉ bộ nhớ một cách tinh vi

function foo(arg) {
    window.bar = "this is an explicit global variable";
}
6

Đoạn mã này làm một việc. mỗi khi

function foo() {
    this.variable = "potential accidental global";
}

// Foo called on its own, this points to the global object (window)
// rather than being undefined.
foo();
0 được gọi,
function foo() {
    this.variable = "potential accidental global";
}

// Foo called on its own, this points to the global object (window)
// rather than being undefined.
foo();
1 sẽ nhận được một đối tượng mới chứa một mảng lớn và một bao đóng mới (
function foo() {
    this.variable = "potential accidental global";
}

// Foo called on its own, this points to the global object (window)
// rather than being undefined.
foo();
2). Đồng thời, biến
function foo() {
    this.variable = "potential accidental global";
}

// Foo called on its own, this points to the global object (window)
// rather than being undefined.
foo();
3 giữ một bao đóng có tham chiếu đến
function foo() {
    this.variable = "potential accidental global";
}

// Foo called on its own, this points to the global object (window)
// rather than being undefined.
foo();
4 (
function foo() {
    this.variable = "potential accidental global";
}

// Foo called on its own, this points to the global object (window)
// rather than being undefined.
foo();
1 từ lần gọi trước tới
function foo() {
    this.variable = "potential accidental global";
}

// Foo called on its own, this points to the global object (window)
// rather than being undefined.
foo();
0). Đã hơi khó hiểu, huh? . Trong trường hợp này, phạm vi được tạo cho việc đóng
function foo() {
    this.variable = "potential accidental global";
}

// Foo called on its own, this points to the global object (window)
// rather than being undefined.
foo();
2 được chia sẻ bởi
function foo() {
    this.variable = "potential accidental global";
}

// Foo called on its own, this points to the global object (window)
// rather than being undefined.
foo();
3.
function foo() {
    this.variable = "potential accidental global";
}

// Foo called on its own, this points to the global object (window)
// rather than being undefined.
foo();
3 có tham chiếu đến
function foo() {
    this.variable = "potential accidental global";
}

// Foo called on its own, this points to the global object (window)
// rather than being undefined.
foo();
4. Mặc dù
function foo() {
    this.variable = "potential accidental global";
}

// Foo called on its own, this points to the global object (window)
// rather than being undefined.
foo();
3 không bao giờ được sử dụng, nhưng
function foo() {
    this.variable = "potential accidental global";
}

// Foo called on its own, this points to the global object (window)
// rather than being undefined.
foo();
2 có thể được sử dụng thông qua
function foo() {
    this.variable = "potential accidental global";
}

// Foo called on its own, this points to the global object (window)
// rather than being undefined.
foo();
1. Và vì
function foo() {
    this.variable = "potential accidental global";
}

// Foo called on its own, this points to the global object (window)
// rather than being undefined.
foo();
2 chia sẻ phạm vi đóng với
function foo() {
    this.variable = "potential accidental global";
}

// Foo called on its own, this points to the global object (window)
// rather than being undefined.
foo();
3, mặc dù
function foo() {
    this.variable = "potential accidental global";
}

// Foo called on its own, this points to the global object (window)
// rather than being undefined.
foo();
3 không bao giờ được sử dụng, tham chiếu của nó đến
function foo() {
    this.variable = "potential accidental global";
}

// Foo called on its own, this points to the global object (window)
// rather than being undefined.
foo();
4 buộc nó phải duy trì hoạt động (ngăn việc thu thập của nó). Khi đoạn mã này được chạy lặp đi lặp lại, có thể quan sát thấy mức sử dụng bộ nhớ tăng đều đặn. Điều này không nhỏ hơn khi GC chạy. Về bản chất, một danh sách các bao đóng được liên kết được tạo (với gốc của nó ở dạng biến
function foo() {
    this.variable = "potential accidental global";
}

// Foo called on its own, this points to the global object (window)
// rather than being undefined.
foo();
1) và mỗi phạm vi của các bao đóng này mang một tham chiếu gián tiếp đến mảng lớn, dẫn đến rò rỉ khá lớn

Đây là một tạo tác thực hiện. Có thể hình dung được một cách triển khai đóng cửa khác có thể xử lý vấn đề này, như được giải thích trong bài đăng trên blog của Meteor

Hành vi không trực quan của Người thu gom rác

Mặc dù Công cụ thu gom rác tiện lợi nhưng chúng đi kèm với sự đánh đổi của riêng chúng. Một trong những sự đánh đổi đó là tính không xác định. Nói cách khác, GC là không thể đoán trước. Thường không thể chắc chắn khi nào một bộ sưu tập sẽ được thực hiện. Điều này có nghĩa là trong một số trường hợp, nhiều bộ nhớ hơn thực tế mà chương trình yêu cầu đang được sử dụng. Trong các trường hợp khác, tạm dừng ngắn có thể được chú ý trong các ứng dụng đặc biệt nhạy cảm. Mặc dù tính không xác định có nghĩa là người ta không thể chắc chắn khi nào một bộ sưu tập sẽ được thực hiện, nhưng hầu hết các triển khai GC đều chia sẻ mô hình chung về việc thực hiện chuyển bộ sưu tập trong quá trình phân bổ. Nếu không có phân bổ nào được thực hiện, hầu hết các GC sẽ ở trạng thái nghỉ. Xem xét tình huống sau

  1. Một tập hợp phân bổ khá lớn được thực hiện
  2. Hầu hết các phần tử này (hoặc tất cả chúng) được đánh dấu là không thể truy cập được (giả sử chúng ta hủy tham chiếu trỏ đến bộ đệm mà chúng ta không cần nữa)
  3. Không phân bổ thêm được thực hiện

Trong trường hợp này, hầu hết các Tổng công ty sẽ không chạy thêm bất kỳ lượt thu thập nào nữa. Nói cách khác, mặc dù có sẵn các tài liệu tham khảo không thể truy cập được để thu thập, nhưng những tài liệu tham khảo này không được người thu thập xác nhận quyền sở hữu. Đây không hẳn là rò rỉ, nhưng vẫn dẫn đến việc sử dụng bộ nhớ cao hơn bình thường

Google cung cấp một ví dụ tuyệt vời về hành vi này trong tài liệu Cấu hình bộ nhớ JavaScript của họ, ví dụ #2

Tổng quan về công cụ cấu hình bộ nhớ Chrome

Chrome cung cấp một bộ công cụ tuyệt vời để cấu hình việc sử dụng bộ nhớ của mã JavaScript. Có hai quan điểm thiết yếu liên quan đến bộ nhớ. chế độ xem dòng thời gian và chế độ xem hồ sơ

Chế độ xem dòng thời gian

Javascript có bộ thu gom rác không?
Chế độ xem dòng thời gian rất cần thiết trong việc khám phá các mẫu bộ nhớ bất thường trong mã của chúng tôi. Trong trường hợp chúng tôi đang tìm kiếm các rò rỉ lớn, các bước nhảy định kỳ không giảm nhiều như khi chúng phát triển sau một bộ sưu tập là một dấu hiệu đỏ. Trong ảnh chụp màn hình này, chúng ta có thể thấy sự tăng trưởng ổn định của các đối tượng bị rò rỉ có thể trông như thế nào. Ngay cả sau khi bộ sưu tập lớn ở cuối, tổng dung lượng bộ nhớ được sử dụng vẫn cao hơn lúc đầu. Số lượng nút cũng cao hơn. Đây là tất cả các dấu hiệu của các nút DOM bị rò rỉ ở đâu đó trong mã

Chế độ xem hồ sơ

Javascript có bộ thu gom rác không?
Đây là chế độ xem bạn sẽ dành phần lớn thời gian để xem. Chế độ xem hồ sơ cho phép bạn chụp nhanh và so sánh các ảnh chụp nhanh về việc sử dụng bộ nhớ của mã JavaScript của bạn. Nó cũng cho phép bạn ghi lại các phân bổ theo thời gian. Trong mọi chế độ xem kết quả đều có sẵn các loại danh sách khác nhau, nhưng những danh sách phù hợp nhất cho nhiệm vụ của chúng tôi là danh sách tóm tắt và danh sách so sánh

Chế độ xem tóm tắt cho chúng ta cái nhìn tổng quan về các loại đối tượng khác nhau được phân bổ và kích thước tổng hợp của chúng. kích thước nông (tổng của tất cả các đối tượng thuộc một loại cụ thể) và kích thước được giữ lại (kích thước nông cộng với kích thước của các đối tượng khác được giữ lại do đối tượng này). Nó cũng cho chúng ta khái niệm về khoảng cách của một đối tượng so với gốc GC của nó (khoảng cách)

Danh sách so sánh cung cấp cho chúng tôi thông tin giống nhau nhưng cho phép chúng tôi so sánh các ảnh chụp nhanh khác nhau. Điều này đặc biệt hữu ích để tìm rò rỉ

Thí dụ. Tìm rò rỉ bằng Chrome

Về cơ bản có hai loại rò rỉ. rò rỉ gây ra sự gia tăng định kỳ trong việc sử dụng bộ nhớ và rò rỉ xảy ra một lần và không làm tăng thêm bộ nhớ. Vì những lý do rõ ràng, sẽ dễ dàng tìm thấy rò rỉ hơn khi chúng được định kỳ. Đây cũng là những rắc rối nhất. nếu bộ nhớ tăng theo thời gian, các rò rỉ kiểu này cuối cùng sẽ khiến trình duyệt trở nên chậm hoặc ngừng thực thi tập lệnh. Rò rỉ không định kỳ có thể dễ dàng được tìm thấy khi chúng đủ lớn để có thể nhận thấy trong số tất cả các phân bổ khác. Đây thường không phải là trường hợp, vì vậy họ thường không được chú ý. Theo một cách nào đó, những rò rỉ nhỏ xảy ra một lần có thể được coi là một vấn đề tối ưu hóa. Tuy nhiên, rò rỉ định kỳ là lỗi và phải được khắc phục

Đối với ví dụ của chúng tôi, chúng tôi sẽ sử dụng một trong các ví dụ trong tài liệu của Chrome. Mã đầy đủ được dán bên dưới

function foo(arg) {
    window.bar = "this is an explicit global variable";
}
6

Khi

var someResource = getData();
setInterval(function() {
    var node = document.getElementById('Node');
    if(node) {
        // Do stuff with node and someResource.
        node.innerHTML = JSON.stringify(someResource));
    }
}, 1000);
9 được gọi, nó sẽ bắt đầu tạo các nút div và nối chúng vào DOM. Nó cũng sẽ phân bổ một mảng lớn và nối nó vào một mảng được tham chiếu bởi một biến toàn cục. Điều này sẽ gây ra sự gia tăng ổn định trong bộ nhớ có thể được tìm thấy bằng cách sử dụng các công cụ được đề cập ở trên

Các ngôn ngữ được thu gom rác thường hiển thị kiểu sử dụng bộ nhớ dao động. Điều này được mong đợi nếu mã đang chạy trong vòng lặp thực hiện phân bổ, đây là trường hợp thông thường. Chúng tôi sẽ tìm kiếm sự gia tăng định kỳ trong bộ nhớ không quay trở lại các mức trước đó sau khi thu thập

Tìm hiểu xem bộ nhớ có tăng định kỳ không

Chế độ xem dòng thời gian là tuyệt vời cho việc này. Mở ví dụ trong Chrome, mở Công cụ dành cho nhà phát triển, chuyển đến dòng thời gian, chọn bộ nhớ và nhấp vào nút ghi. Sau đó chuyển đến trang và nhấp vào

function foo(arg) {
    window.bar = "this is an explicit global variable";
}
20 để bắt đầu rò rỉ bộ nhớ. Sau một lúc, dừng ghi âm và xem kết quả

Javascript có bộ thu gom rác không?

Ví dụ này sẽ tiếp tục rò rỉ bộ nhớ mỗi giây. Sau khi dừng ghi, hãy đặt điểm ngắt trong hàm

var someResource = getData();
setInterval(function() {
    var node = document.getElementById('Node');
    if(node) {
        // Do stuff with node and someResource.
        node.innerHTML = JSON.stringify(someResource));
    }
}, 1000);
9 để ngăn tập lệnh buộc Chrome đóng trang

Có hai dấu hiệu lớn trong hình ảnh này cho thấy chúng tôi đang rò rỉ bộ nhớ. Biểu đồ cho các nút (đường màu xanh lá cây) và đống JS (đường màu xanh lam). Các nút đang tăng đều đặn và không bao giờ giảm. Đây là một dấu hiệu cảnh báo lớn

Heap JS cũng cho thấy mức sử dụng bộ nhớ tăng đều đặn. Điều này khó thấy hơn do ảnh hưởng của bộ thu gom rác. Bạn có thể thấy mô hình tăng trưởng bộ nhớ ban đầu, tiếp theo là giảm mạnh, tiếp theo là tăng và sau đó tăng đột biến, tiếp theo là giảm bộ nhớ khác. Chìa khóa trong trường hợp này nằm ở chỗ sau mỗi lần giảm mức sử dụng bộ nhớ, kích thước của heap vẫn lớn hơn lần giảm trước đó. Nói cách khác, mặc dù bộ thu gom rác đang thành công trong việc thu thập rất nhiều bộ nhớ, nhưng một số bộ nhớ bị rò rỉ theo định kỳ.

Bây giờ chúng tôi chắc chắn rằng chúng tôi có một rò rỉ. Hãy tìm nó

Nhận hai ảnh chụp nhanh

Để tìm rò rỉ, bây giờ chúng ta sẽ chuyển đến phần hồ sơ của Công cụ dành cho nhà phát triển của Chrome. Để duy trì việc sử dụng bộ nhớ ở mức có thể quản lý được, hãy tải lại trang trước khi thực hiện bước này. Chúng ta sẽ sử dụng chức năng Take Heap Snapshot

Tải lại trang và chụp nhanh heap ngay sau khi tải xong. Chúng tôi sẽ sử dụng ảnh chụp nhanh này làm cơ sở của chúng tôi. Sau đó, nhấn lại

function foo(arg) {
    window.bar = "this is an explicit global variable";
}
20, đợi vài giây và chụp ảnh nhanh lần thứ hai. Sau khi chụp ảnh nhanh, bạn nên đặt điểm dừng trong tập lệnh để ngăn rò rỉ sử dụng thêm bộ nhớ

Javascript có bộ thu gom rác không?

Có hai cách để chúng ta có thể xem xét phân bổ giữa hai ảnh chụp nhanh. Chọn Tóm tắt rồi sang bên phải chọn Đối tượng được phân bổ giữa Ảnh chụp nhanh 1 và Ảnh chụp nhanh 2 hoặc chọn So sánh thay vì Tóm tắt. Trong cả hai trường hợp, chúng ta sẽ thấy một danh sách các đối tượng được phân bổ giữa hai ảnh chụp nhanh

Trong trường hợp này, khá dễ dàng để tìm ra chỗ rò rỉ. họ to lớn. Hãy xem

function foo(arg) {
    window.bar = "this is an explicit global variable";
}
23 của hàm tạo
function foo(arg) {
    window.bar = "this is an explicit global variable";
}
24. 8 MB với 58 đối tượng mới. Điều này có vẻ đáng ngờ. các đối tượng mới được phân bổ nhưng không được giải phóng và 8 MB được sử dụng

Nếu chúng tôi mở danh sách phân bổ cho hàm tạo

function foo(arg) {
    window.bar = "this is an explicit global variable";
}
24, chúng tôi sẽ nhận thấy có một vài phân bổ lớn trong số nhiều phân bổ nhỏ. Những người lớn ngay lập tức thu hút sự chú ý của chúng tôi. Nếu chúng tôi chọn bất kỳ một trong số chúng, chúng tôi sẽ nhận được một cái gì đó thú vị trong phần người lưu giữ bên dưới

Javascript có bộ thu gom rác không?

Chúng tôi thấy phân bổ đã chọn của chúng tôi là một phần của một mảng. Đổi lại, mảng được tham chiếu bởi biến

function foo(arg) {
    window.bar = "this is an explicit global variable";
}
26 bên trong đối tượng toàn cục
function foo() {
    this.variable = "potential accidental global";
}

// Foo called on its own, this points to the global object (window)
// rather than being undefined.
foo();
0. Điều này cung cấp cho chúng tôi một đường dẫn đầy đủ từ đối tượng lớn của chúng tôi đến gốc không thể thu thập được của nó (
function foo() {
    this.variable = "potential accidental global";
}

// Foo called on its own, this points to the global object (window)
// rather than being undefined.
foo();
0). Chúng tôi đã tìm thấy khả năng rò rỉ của chúng tôi và nơi nó được tham chiếu

Càng xa càng tốt. Nhưng ví dụ của chúng tôi rất dễ dàng. phân bổ lớn như phân bổ trong ví dụ này không phải là tiêu chuẩn. May mắn thay, ví dụ của chúng tôi cũng bị rò rỉ các nút DOM, nhỏ hơn. Có thể dễ dàng tìm thấy các nút này bằng cách sử dụng ảnh chụp nhanh ở trên, nhưng ở các trang web lớn hơn, mọi thứ trở nên lộn xộn hơn. Các phiên bản Chrome gần đây cung cấp một công cụ bổ sung phù hợp nhất cho công việc của chúng tôi. chức năng Phân bổ Heap Bản ghi

Ghi phân bổ heap để tìm rò rỉ

Vô hiệu hóa điểm ngắt mà bạn đã đặt trước đó, để tập lệnh tiếp tục chạy và quay lại phần Cấu hình trong Công cụ dành cho nhà phát triển của Chrome. Bây giờ nhấn Record Heap Allocations. Trong khi công cụ đang chạy, bạn sẽ thấy các gai màu xanh trong biểu đồ ở trên cùng. Chúng đại diện cho sự phân bổ. Mỗi giây, một phân bổ lớn được thực hiện bởi mã của chúng tôi. Chạy vài giây rồi dừng lại (đừng quên set lại breakpoint để tránh Chrome ngốn thêm bộ nhớ)

Javascript có bộ thu gom rác không?

Trong hình ảnh này, bạn có thể thấy tính năng sát thủ của công cụ này. chọn một phần của dòng thời gian để xem phân bổ nào được thực hiện trong khoảng thời gian đó. Chúng tôi đặt vùng chọn càng gần với một trong những gai lớn càng tốt. Chỉ có ba hàm tạo được hiển thị trong danh sách. một trong số chúng là cái liên quan đến rò rỉ lớn của chúng tôi (

function foo(arg) {
    window.bar = "this is an explicit global variable";
}
24), cái tiếp theo liên quan đến phân bổ DOM và cái cuối cùng là hàm tạo
function foo(arg) {
    window.bar = "this is an explicit global variable";
}
40 (hàm tạo cho các nút DOM lá chứa văn bản)

Chọn một trong các hàm tạo

function foo(arg) {
    window.bar = "this is an explicit global variable";
}
41 từ danh sách rồi chọn
function foo(arg) {
    window.bar = "this is an explicit global variable";
}
42

Javascript có bộ thu gom rác không?

BẠM. Bây giờ chúng ta biết phần tử đó được phân bổ ở đâu (______99 ->

function foo(arg) {
    window.bar = "this is an explicit global variable";
}
44). Nếu chúng ta chú ý đến từng điểm tăng đột biến trong biểu đồ, chúng ta sẽ nhận thấy rằng hàm tạo
function foo(arg) {
    window.bar = "this is an explicit global variable";
}
41 đang được gọi rất nhiều. Nếu chúng tôi quay lại chế độ xem so sánh ảnh chụp nhanh của mình, chúng tôi sẽ nhận thấy rằng hàm tạo này hiển thị nhiều phân bổ nhưng không xóa. Nói cách khác, nó đang phân bổ bộ nhớ một cách đều đặn mà không cho phép GC lấy lại một phần của nó. Điều này có tất cả các dấu hiệu rò rỉ cộng với việc chúng tôi biết chính xác nơi các đối tượng này đang được phân bổ (hàm
function foo(arg) {
    window.bar = "this is an explicit global variable";
}
44). Bây giờ là lúc để quay lại mã, nghiên cứu và sửa lỗi rò rỉ

Một tính năng hữu ích khác

Trong chế độ xem kết quả phân bổ heap, chúng ta có thể chọn chế độ xem Phân bổ thay vì Tóm tắt

Javascript có bộ thu gom rác không?

Chế độ xem này cung cấp cho chúng tôi danh sách các chức năng và cấp phát bộ nhớ liên quan đến chúng. Chúng ta có thể thấy ngay

var someResource = getData();
setInterval(function() {
    var node = document.getElementById('Node');
    if(node) {
        // Do stuff with node and someResource.
        node.innerHTML = JSON.stringify(someResource));
    }
}, 1000);
9 và
function foo(arg) {
    window.bar = "this is an explicit global variable";
}
44 nổi bật. Khi chọn
var someResource = getData();
setInterval(function() {
    var node = document.getElementById('Node');
    if(node) {
        // Do stuff with node and someResource.
        node.innerHTML = JSON.stringify(someResource));
    }
}, 1000);
9, chúng ta sẽ xem các hàm tạo đối tượng liên quan được gọi bởi nó. Chúng tôi nhận thấy
function foo(arg) {
    window.bar = "this is an explicit global variable";
}
24,
function foo(arg) {
    window.bar = "this is an explicit global variable";
}
41 và
function foo(arg) {
    window.bar = "this is an explicit global variable";
}
40 mà bây giờ chúng tôi đã biết là các hàm tạo của các đối tượng bị rò rỉ

Sự kết hợp của các công cụ này có thể giúp ích rất nhiều trong việc tìm kiếm rò rỉ. Chơi với chúng. Thực hiện các chạy định hình khác nhau trong các trang web sản xuất của bạn (lý tưởng nhất là với mã không được thu nhỏ hoặc mã bị xáo trộn). Xem liệu bạn có thể tìm thấy chỗ rò rỉ hoặc đồ vật được giữ lại nhiều hơn mức cần thiết hay không (gợi ý. những thứ này khó tìm hơn)

Để sử dụng tính năng này, hãy chuyển đến Công cụ dành cho nhà phát triển -> Cài đặt và bật "ghi lại dấu vết ngăn xếp phân bổ heap". Cần phải làm điều này trước khi ghi âm

đọc thêm

  • Quản lý bộ nhớ - Mạng nhà phát triển Mozilla
  • Rò rỉ bộ nhớ JScript - Douglas Crockford (cũ, liên quan đến rò rỉ Internet Explorer 6)
  • Cấu hình bộ nhớ JavaScript - Tài liệu dành cho nhà phát triển Chrome
  • Chẩn đoán bộ nhớ - Google Developers
  • Một kiểu rò rỉ bộ nhớ JavaScript thú vị - blog Meteor
  • Grokking V8 đóng cửa

Qua một bên. sử dụng Thư viện JavaScript của Auth0 để xác thực

Tại Auth0, chúng tôi sử dụng JavaScript rất nhiều. Sử dụng máy chủ xác thực và ủy quyền của chúng tôi từ các ứng dụng web JavaScript của bạn thật dễ dàng. Đây là một ví dụ đơn giản sử dụng các tính năng của ECMAScript 2015 và Auth0. thư viện js

Đây là tập lệnh phía máy khách chính để xác thực và ủy quyền cho người dùng truy cập API. Nó cũng cập nhật DOM để hiển thị một số dữ liệu người dùng

function foo(arg) {
    window.bar = "this is an explicit global variable";
}
9

Lấy ví dụ hoạt động đầy đủ và đăng ký tài khoản miễn phí để tự mình dùng thử

Sự kết luận

Rò rỉ bộ nhớ có thể và đã xảy ra trong các ngôn ngữ được thu gom rác chẳng hạn như JavaScript. Những thứ này có thể không được chú ý trong một thời gian và cuối cùng chúng sẽ tàn phá. Vì lý do này, các công cụ lập hồ sơ bộ nhớ rất cần thiết để tìm rò rỉ bộ nhớ. Chạy hồ sơ phải là một phần của chu trình phát triển, đặc biệt đối với các ứng dụng cỡ trung bình hoặc lớn. Bắt đầu làm điều này để cung cấp cho người dùng của bạn trải nghiệm tốt nhất có thể. hack vào

Bộ sưu tập rác JavaScript hoạt động như thế nào?

Trình thu gom rác bén rễ và “đánh dấu” (ghi nhớ) chúng. Sau đó, nó truy cập và “đánh dấu” tất cả các tham chiếu từ chúng. Sau đó, nó truy cập các đối tượng được đánh dấu và đánh dấu các tham chiếu của chúng . Tất cả các đối tượng đã truy cập đều được ghi nhớ để không truy cập cùng một đối tượng hai lần trong tương lai.

Ngôn ngữ nào có bộ thu gom rác?

Thu gom rác (GC) là tính năng khôi phục bộ nhớ được tích hợp trong các ngôn ngữ lập trình như C# và Java .

Ngôn ngữ nào không có bộ thu gom rác?

Các ngôn ngữ lập trình nguyên thủy như C và C++ không có bộ sưu tập rác thay vào đó mong muốn nhà phát triển không chỉ phân bổ đối tượng mà còn phân bổ đối tượng đó . Do đó, chúng tôi thấy các chức năng như "malloc" và "free".

Có trình thu gom rác trong Java không?

Thu gom rác Java là một quy trình tự động . Thu gom rác tự động là quá trình xem xét bộ nhớ heap, xác định đối tượng nào đang được sử dụng và đối tượng nào không, đồng thời xóa các đối tượng không sử dụng.