Xóa bỏ rò rỉ bộ nhớ trong Javascript


Lê Thanh Tú
1 năm trước
Hữu ích 4 Chia sẻ Viết bình luận 0
Đã xem 6508

Nếu bạn đang tự hỏi tại sao ứng dụng JavaScript của bạn bị chậm lại nghiêm trọng, hiệu suất kém, độ trễ cao hoặc sự cố thường xuyên và tất cả các nỗ lực cố gắng của bạn để tìm ra vấn đề là không có kết quả, thì rất có khả năng mã của bạn bị vướng bởi 'Rò rỉ bộ nhớ.' Rò rỉ bộ nhớ khá phổ biến vì việc quản lý bộ nhớ thường bị các nhà phát triển bỏ qua do những quan niệm sai lầm về phân bổ và giải phóng bộ nhớ tự động trong các ngôn ngữ lập trình cấp cao hiện đại như  JavaScript. Việc không xử lý rò rỉ bộ nhớ có thể ảnh hưởng đến hiệu suất của ứng dụng và có thể khiến ứng dụng không thể sử dụng được. Internet tràn ngập những biệt ngữ phức tạp không bao giờ kết thúc, thường rất khó khăn để quấn đầu bạn. Vì vậy, trong bài viết này, chúng tôi sẽ thực hiện một cách tiếp cận toàn diện để hiểu rò rỉ bộ nhớ là gì, nguyên nhân của chúng và cách phát hiện và chẩn đoán chúng dễ dàng bằng Công cụ dành cho nhà phát triển Chrome.

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

Rò rỉ bộ nhớ có thể được định nghĩa là một phần bộ nhớ không còn được sử dụng hoặc yêu cầu bởi một ứng dụng nhưng vì một lý do nào đó không được đưa trở lại HĐH và vẫn bị chiếm dụng một cách không cần thiết. Tạo các đối tượng và các biến trong mã của bạn tiêu thụ bộ nhớ. JavaScript đủ thông minh để tìm ra khi nào bạn sẽ không cần biến nữa và sẽ xóa nó để tiết kiệm bộ nhớ. Rò rỉ bộ nhớ xảy ra khi bạn có thể không còn cần một đối tượng nhưng thời gian chạy JS vẫn nghĩ bạn làm. Ngoài ra, hãy nhớ rằng rò rỉ bộ nhớ không phải do mã không hợp lệ mà là một lỗ hổng logic trong mã của bạn. Nó dẫn đến hiệu suất giảm dần của ứng dụng của bạn bằng cách giảm dung lượng bộ nhớ có sẵn để thực hiện các tác vụ và cuối cùng có thể dẫn đến sự cố hoặc đóng băng.

Trước khi đi sâu vào rò rỉ bộ nhớ, điều quan trọng là phải hiểu âm thanh về chu kỳ bộ nhớ, hệ thống quản lý bộ nhớ và thuật toán thu gom rác.

Chu kỳ bộ nhớ là gì?

Một phần của 'bộ nhớ' bao gồm một loạt các flip-flop, đó là một mạch 2 trạng thái (0 và 1) bao gồm 4 đến 6 bóng bán dẫn. Khi flip-flop lưu trữ một chút, nó sẽ tiếp tục giữ lại cho đến khi nó được viết lại với bit đối diện. Vì vậy, bộ nhớ là một mảng các bit có thể lập trình lại. Mỗi và mọi phần dữ liệu được sử dụng trong một chương trình được lưu trữ trong bộ nhớ. Một chu kỳ bộ nhớ là chuỗi các sự kiện hoàn chỉnh để một đơn vị bộ nhớ chuyển từ trạng thái không hoạt động / tự do thông qua giai đoạn sử dụng (đọc hoặc ghi) và trở về trạng thái không hoạt động.

Vòng đời bộ nhớ có thể được chia thành 3 bước chính:

  1. Phân bổ bộ nhớ : bộ nhớ được HĐH cấp phát cho chương trình trong khi thực hiện khi cần. Trong các ngôn ngữ cấp thấp như C và C ++, bước này được lập trình viên xử lý, nhưng trong các ngôn ngữ cấp cao như JavaScript, hệ thống này tự thực hiện bởi hệ thống quản lý bộ nhớ tự động. Đây là một ví dụ về phân bổ bộ nhớ trong JavaScript:
    var n = 5; // allocates memory for a number
       var s = 'Hello World'; // allocates memory for a string
       var obj = { // allocates memory for an object
           a: 100,
           b: "some string",
           c: null,
       };
       var arr = [100, "some string", null]; // allocates memory for the array
       function foo(x, y) { // allocates memory for a function
           return x * y;
       }

 2. Sử dụng bộ nhớ: Chương trình thực hiện các chức năng đọc và ghi trên bộ nhớ được phân bổ. Điều này có thể là đọc hoặc ghi giá trị của một biến, một đối tượng hoặc thậm chí truyền một đối số cho một hàm.

 3. Giải phóng bộ nhớ: Khi tác vụ kết thúc và bộ nhớ được phân bổ không còn cần thiết, nó sẽ được giải phóng và miễn phí cho các phân bổ mới.

Bước thứ ba của chu kỳ bộ nhớ là nơi các biến chứng nằm. Thách thức khó khăn nhất ở đây là xác định khi nào bộ nhớ được phân bổ không còn cần thiết nữa và cần được giải phóng. Đây là nơi các hệ thống quản lý bộ nhớ và thuật toán thu gom rác của chúng được giải cứu.

Hệ thống quản lý bộ nhớ: Hướng dẫn so với tự động

Các ngôn ngữ lập trình khác nhau sử dụng các cách tiếp cận khác nhau tùy thuộc vào độ phức tạp của chúng để đối phó với việc quản lý bộ nhớ.

  • Các ngôn ngữ lập trình cấp thấp như Pascal, C và C ++ có hệ thống quản lý bộ nhớ thủ công trong đó người lập trình phải phân bổ thủ công / rõ ràng bộ nhớ khi cần và sau đó giải phóng bộ nhớ sau khi chương trình được sử dụng. Ví dụ, C sử dụng malloc() một thứ calloc() để dành bộ nhớ,  realloc() để di chuyển một khối reserved bộ nhớ để phân bổ khác, và  free() để giải phóng bộ nhớ trở lại vào hệ thống.
  • Các ngôn ngữ lập trình cấp cao như JavaScript và VB có một hệ thống tự động phân bổ bộ nhớ mỗi khi bạn tạo một thực thể như một đối tượng, một mảng, một chuỗi hoặc một phần tử DOM và tự động giải phóng nó khi chúng không còn được sử dụng nữa, bởi một quá trình gọi là thu gom rác. Rò rỉ bộ nhớ xảy ra khi chương trình của bạn vẫn đang tiêu thụ bộ nhớ, lý tưởng nên được giải phóng sau khi hoàn thành nhiệm vụ nhất định. Nếu vì một lý do nào đó, trình thu gom rác không phục vụ mục đích của nó và chương trình từ chối giải phóng bộ nhớ, bộ nhớ sẽ tiếp tục bị tiêu thụ mà không cần nó xảy ra.

Thu gom rác

Trình thu gom rác thực hiện quá trình tìm bộ nhớ không còn được sử dụng bởi chương trình và giải phóng nó trở lại HĐH để phân bổ lại trong tương lai. Để tìm bộ nhớ không còn được sử dụng, người thu gom rác dựa vào thuật toán. Mặc dù phương pháp thu gom rác có hiệu quả cao, nhưng vẫn có thể xảy ra rò rỉ bộ nhớ trong JavaScript. Nguyên nhân chính cho những rò rỉ như vậy thường là một 'tài liệu tham khảo không mong muốn'. Lý do chính cho điều này là do quá trình thu gom rác dựa trên các ước tính hoặc phỏng đoán, vì vấn đề phức tạp về việc một số bộ nhớ cần được giải phóng có thể được xác định chính xác bởi một thuật toán trong mọi trường hợp.

Trước khi tiến xa hơn, chúng ta hãy xem hai thuật toán thu gom rác được sử dụng rộng rãi nhất.

Giống như chúng ta đã thảo luận trước đó, bất kỳ thuật toán thu gom rác nào cũng phải thực hiện hai chức năng cơ bản. Nó phải có khả năng phát hiện tất cả bộ nhớ không còn sử dụng và nó phải giải phóng / phân bổ không gian được sử dụng bởi các đối tượng rác và làm cho nó có sẵn một lần nữa để tái phân bổ trong tương lai nếu cần.

Hai thuật toán phổ biến nhất là:

  1. Số tham chiếu
  2. Đánh dấu và quét

Thuật toán đếm tham chiếu

Thuật toán này dựa trên khái niệm 'tham chiếu'. Nó dựa trên việc đếm số lượng tham chiếu đến một đối tượng từ các đối tượng khác. Mỗi khi một đối tượng được tạo hoặc một tham chiếu đến đối tượng được gán, số tham chiếu của nó sẽ tăng lên. Trong JavaScript, mọi đối tượng đều có tham chiếu ngầm đến nguyên mẫu và tham chiếu rõ ràng đến các giá trị thuộc tính của nó. Các thuật toán đếm tham chiếu là các thuật toán thu gom rác cơ bản nhất. Chúng làm giảm định nghĩa của một đối tượng không còn cần thiết nữa, vì vậy, đối với một đối tượng không có đối tượng nào khác tham chiếu đến nó. Một đối tượng được coi là rác thu gom và được coi là không còn được sử dụng nếu không có tham chiếu nào chỉ vào nó.

       var o = { // 2 objects are created. One is referenced by the other as one of its properties.
           a: { // The other is referenced by virtue of being assigned to the 'o' variable.
               b: 2; // Obviously, none can be garbage-collected
           }
       };

       var o2 = o; // the 'o2' variable is the second thing that has a reference to the object
       o = 1; // now, the object that was originally in 'o' has a unique reference embodied by the 'o2' variable
       var oa = o2.a; // reference to 'a' property of the object.This object now has 2 references: one as a property,
       // the other as the 'oa' variable
       o2 = 'yo'; // The object that was originally in 'o' has now zero references to it. It can be garbage-collected.
       // However its 'a' property is still referenced by the 'oa' variable, so it cannot be freed
       oa = null; // The 'a' property of the object originally in o has zero references to it. It can be garbage collected.
       };

Hạn chế của thuật toán đếm tham chiếu

Tuy nhiên, có một hạn chế lớn đối với các thuật toán đếm tham chiếu trong trường hợp chu kỳ.

Một chu trình là một thể hiện trong đó hai đối tượng được tạo bằng cách tham chiếu lẫn nhau. Vì cả hai đối tượng đều có số tham chiếu ít nhất một (được tham chiếu ít nhất một lần), thuật toán thu gom rác không thu thập chúng ngay cả khi chúng không còn được sử dụng.

      function foo() {
           var obj1 = {};
           var obj2 = {};
           obj1.x = obj2; // obj1 references obj2
           obj2.x = obj1; // obj2 references obj1

           return true;
       }
       foo();

Thuật toán đánh dấu và quét

Không giống như thuật toán đếm tham chiếu, việc đánh dấu và quét làm giảm định nghĩa của một đối tượng không còn cần thiết nữa.

Người thu gom rác trước tiên sẽ tìm thấy tất cả các đối tượng gốc và sẽ ánh xạ tất cả các tham chiếu đến các đối tượng toàn cầu này và các tham chiếu đến các đối tượng đó, v.v. Sử dụng thuật toán này, trình thu gom rác xác định tất cả các đối tượng có thể truy cập và rác thu thập tất cả các đối tượng không thể truy cập.

Các thuật toán đánh dấu và quét hoạt động theo hai giai đoạn:

  1. Giai đoạn đánh dấu: Mỗi khi một đối tượng được tạo, bit đánh dấu của nó được đặt thành 0 (sai). Trong Pha Mark, bit đánh dấu của mọi đối tượng 'có thể tiếp cận' được thay đổi và được đặt thành 1 (đúng).
  2. Pha quét: Tất cả những đối tượng có bit đánh dấu vẫn được đặt thành 0 (sai) sau Pha Mark là các đối tượng không thể truy cập và do đó, thuật toán được thu thập và giải phóng khỏi bộ nhớ.

Tất cả các đối tượng ban đầu có các bit được đánh dấu của chúng được đặt thành 0 (sai).

Tất cả các đối tượng có thể truy cập có bit được đánh dấu của chúng thay đổi thành 1 (đúng).

Các đối tượng không thể truy cập được xóa khỏi bộ nhớ.

Ưu điểm của thuật toán Mark-and-Sweep

Không giống như các thuật toán đếm tham chiếu, đánh dấu và quét giao dịch với các chu kỳ. Hai đối tượng trong một chu kỳ không được tham chiếu bởi bất cứ thứ gì có thể truy cập từ gốc. Chúng được coi là không thể truy cập bởi người thu gom rác và bị cuốn đi.

Hạn chế của thuật toán đánh dấu và quét

Nhược điểm chính của phương pháp này là việc thực thi chương trình bị đình chỉ trong khi thuật toán thu gom rác chạy.

Nguyên nhân rò rỉ bộ nhớ

Trong ngữ cảnh của JavaScript, chìa khóa lớn nhất để ngăn chặn rò rỉ bộ nhớ nằm ở sự hiểu biết về cách tạo các tham chiếu không mong muốn. Tùy thuộc vào bản chất của các tham chiếu không mong muốn này, chúng tôi có thể phân loại các nguồn bộ nhớ thành 7 loại:

1. Biến không được khai báo / tai nạn toàn cầu

JavaScript có hai loại phạm vi - phạm vi cục bộ và phạm vi toàn cầu. Phạm vi xác định mức độ hiển thị của các biến, hàm và đối tượng trong thời gian chạy.

  • Các biến phạm vi cục bộ chỉ có thể truy cập và hiển thị trong phạm vi cục bộ của chúng (nơi chúng được xác định). Các biến cục bộ được cho là có 'Phạm vi hàm': chúng chỉ có thể được truy cập từ bên trong hàm.
       // Outside myFunction() variable ‘a’ cannot be accessed
       function myFunction() {
           var a = "This is a local scope variable";
           // variable ‘a’ is accessible only inside myFunction()
       }
  • Mặt khác, các biến có phạm vi toàn cầu có thể được truy cập bởi tất cả các tập lệnh và hàm trong tài liệu JavaScript. Khi bạn bắt đầu viết JavaScript trong một tài liệu, bạn đã ở trong phạm vi toàn cầu. Không giống như phạm vi cục bộ, chỉ có một phạm vi toàn cầu trong toàn bộ tài liệu JavaScript. Tất cả các biến toàn cục thuộc về đối tượng cửa sổ. Nếu bạn gán giá trị cho một biến chưa được khai báo trước đó, nó sẽ tự động trở thành 'biến toàn cục'. Biến toàn cục không bị xóa bởi trình thu gom rác
       // variable ‘a’ can be accessed globally
       var a = "This is a global variable";

       function myFunction() {
           // the variable a is accessible here inside the myFunction() as well
       }


Trường hợp biến toàn cầu tình cờ

Nếu bạn chỉ định một giá trị cho một biến mà không cần khai báo trước, nó sẽ tạo ra một biến toàn cục 'tự động' hoặc 'vô tình'. Ví dụ này sẽ khai báo một biến toàn cục, ngay cả khi nó được gán một giá trị bên trong hàm.

       // variable ‘a’ has global scope
       function myFunction() {
           a = "this is an accidental global variable";
           // variable ‘a’ is global as it has been assigned a value without prior declaration
       }

Giải pháp:  Các biến toàn cầu, theo định nghĩa, không bị quét bởi người thu gom rác. Đây là lý do tại sao một lập trình viên phải sử dụng các biến toàn cục một cách cẩn thận và không bao giờ quên null hoặc gán lại nó sau khi sử dụng. Trong ví dụ trên, đặt biến toàn cục a thành null sau lệnh gọi hàm. Một cách khác là sử dụng chế độ 'nghiêm ngặt' để phân tích mã JS của bạn. Điều này sẽ ngăn chặn việc tạo ra các biến toàn cầu vô tình không được khai báo. Một cách khác là sử dụng 'let' thay vì 'var' để khai báo biến. Hãy có một phạm vi khối. Phạm vi của nó được giới hạn trong một khối, một tuyên bố hoặc một biểu thức. Điều này không giống như từ khóa var, định nghĩa một biến trên toàn cầu.

2. Đóng cửa

Một bao đóng là sự kết hợp của một hàm và môi trường từ vựng trong đó hàm đó được khai báo. Một bao đóng là một hàm bên trong (kèm theo) có quyền truy cập vào các biến (phạm vi) của hàm ngoài (bao vây). Ngoài ra, chức năng bên trong sẽ tiếp tục có quyền truy cập vào phạm vi của chức năng bên ngoài ngay cả sau khi chức năng bên ngoài được thực thi.

Rò rỉ bộ nhớ xảy ra trong một bao đóng nếu một biến được khai báo ở hàm ngoài và nó sẽ tự động có sẵn cho hàm bên trong lồng nhau và tiếp tục nằm trong bộ nhớ ngay cả khi nó không được sử dụng / được tham chiếu trong hàm lồng nhau.

   var newElem;
   function outer() {
       var someText = new Array(1000000);
       var elem = newElem;
       function inner() {
           if (elem) return someText;
       }
       return function () {};
   }
   setInterval(function () {
       newElem = outer();
   }, 5);

Trong ví dụ trên, hàm bên trong không bao giờ được gọi mà giữ tham chiếu đến hàm ngoài. Nhưng vì tất cả các hàm bên trong trong một bao đóng đều có chung một bối cảnh,  inner() (dòng 5) chia sẻ cùng một bối cảnh với   function() {} (dòng 8) được trả về bởi hàm bên ngoài. Bây giờ, cứ sau 5ms chúng ta thực hiện một lệnh gọi hàm  outer và gán giá trị mới của nó (sau mỗi cuộc gọi)  newElem là biến toàn cục. Khi tham chiếu chỉ đến điều này function() {}, phạm vi / bối cảnh được chia sẻ sẽ được giữ nguyên và  someText được giữ vì nó là một phần của hàm bên trong ngay cả khi hàm bên trong không bao giờ được gọi. Mỗi lần chúng ta gọi,  outer chúng ta lưu lại phần trước  function(){} trong   elem hàm mới. Do đó, một lần nữa, phạm vi / bối cảnh được chia sẻ trước đó phải được giữ lại. Vì vậy, trong cuộc gọi thứ n của outer chức năng  someText của cuộc gọi (n-1)  outer không thể được thu gom rác. Quá trình này tiếp tục cho đến khi hệ thống của bạn hết bộ nhớ.

Giải pháp:  Vấn đề, trong trường hợp này, xảy ra vì tham chiếu đến  function() {} được giữ nguyên. Sẽ không có rò rỉ nếu hàm ngoài thực sự được gọi (gọi hàm ngoài trong dòng 15 như   newElem = outer()();). Một rò rỉ nhỏ bị cô lập do đóng cửa có thể không cần bất kỳ sự chú ý. Tuy nhiên, việc lặp lại rò rỉ định kỳ và phát triển với mỗi lần lặp có thể làm hỏng nghiêm trọng hiệu năng của mã của bạn.

3. Tách rời DOM / Ra khỏi tham chiếu DOM

Tham chiếu DOM hoặc Out of DOM ngụ ý rằng các nút đã bị xóa khỏi DOM vẫn được giữ lại trong bộ nhớ thông qua JavaScript. Điều đó có nghĩa là miễn là vẫn còn tham chiếu đến một biến hoặc một đối tượng ở bất kỳ đâu, đối tượng đó không phải là rác được thu thập ngay cả khi đã bị xóa khỏi DOM.

DOM là một cây được liên kết đôi, có tham chiếu đến bất kỳ nút nào trong cây sẽ ngăn toàn bộ cây khỏi bộ sưu tập rác. Chúng ta hãy lấy một ví dụ về việc tạo một phần tử DOM trong JavaScript và sau đó tại một thời điểm nào đó sẽ xóa phần tử này (hoặc phần tử cha của nó) mà quên xóa biến đang giữ nó. Điều này dẫn đến một DOM tách rời chứa tham chiếu đến không chỉ phần tử DOM mà cả toàn bộ cây.

       var demo = document.createElement("p");
       demo.id = "myText";
       document.body.appendChild(demo);
       var lib = {
           text: document.getElementById('myText')
       };

       function createFunction() {
           lib.text.innerHTML = "hello World";
       }
       createFunction();

       function deleteFunction() {
           document.body.removeChild(document.getElementById('myText'));
       }
       deleteFunction();

Ngay cả sau khi xóa #myText khỏi DOM, chúng tôi vẫn có một tham chiếu đến #myText trong đối tượng lib toàn cầu. Đây là lý do tại sao nó không thể được giải phóng bởi bộ thu gom rác và sẽ tiếp tục tiêu thụ bộ nhớ. Đây là một trường hợp rò rỉ bộ nhớ khác phải tránh bằng cách điều chỉnh mã của bạn.

Giải pháp:  Một cách phổ biến là đặt  var demo bên trong người nghe, làm cho nó trở thành một biến cục bộ. Khi  demo bị xóa, đường dẫn cho đối tượng bị cắt. Trình thu gom rác có thể giải phóng bộ nhớ này.

4. Bộ hẹn giờ

Có hai sự kiện thời gian trong JavaScript, đó là  setTimeout và  setInterval.  setTimeout() thực thi một chức năng, sau khi chờ một số mili giây xác định trong khi setInterval() một số nhưng lặp lại việc thực hiện chức năng liên tục. Các  setTimeout() và  setInterval() là cả hai phương pháp của đối tượng HTML DOM Window. Bộ định thời JavaScript là nguyên nhân thường xuyên nhất gây rò rỉ bộ nhớ vì việc sử dụng chúng là khá phổ biến.

Hãy xem xét mã JavaScript sau liên quan đến bộ định thời tạo rò rỉ bộ nhớ:

       for (var i = 0; i < 100000; i++) {
           var buggyObject = {
               callAgain: function() {
                   var ref = this;
                   var val = setTimeout(function() {
                       ref.callAgain();
                   }, 1000000);
               }
           }
           buggyObject.callAgain();
           buggyObject = null;
       }

Gọi lại hẹn giờ và đối tượng bị ràng buộc của nó  buggyObject, sẽ không được phát hành cho đến khi hết thời gian. Trong trường hợp này, bộ định thời đặt lại chính nó và chạy mãi mãi và do đó không gian bộ nhớ của nó sẽ không bao giờ được thu thập ngay cả khi không có tham chiếu đến đối tượng ban đầu.

Giải pháp:  Để tránh trường hợp này, các tham chiếu bên trong một   cuộc gọi setTimeout/ setInterval, chẳng hạn như các hàm, cần được thực thi và hoàn thành trước khi chúng có thể được thu gom rác. Thực hiện một cuộc gọi rõ ràng để loại bỏ chúng một khi bạn không còn cần chúng nữa. Ngoại trừ các trình duyệt cũ như Internet Explorers, phần lớn các trình duyệt hiện đại như Chrome và Firefox sẽ không gặp phải vấn đề này. Ngoài ra, các thư viện như jQuery xử lý nó bên trong để đảm bảo rằng không có rò rỉ nào được tạo ra.

5. Lỗi trình duyệt và tiện ích mở rộng

Các trình duyệt cũ hơn, đặc biệt là IE6-7 đã nổi tiếng vì tạo ra rò rỉ bộ nhớ vì thuật toán thu gom rác của chúng không thể xử lý các tham chiếu vòng tròn giữa các đối tượng DOM và các đối tượng JavaScript. Đôi khi các phần mở rộng trình duyệt bị lỗi cũng có thể là nguyên nhân gây rò rỉ. Ví dụ: khi trình cập nhật Filteret.G trong Firefox được sử dụng với FlashGot, nó sẽ tạo ra rò rỉ bộ nhớ.

6. Người nghe sự kiện

Các  addEventListener() phương pháp gắn một event handler đến một yếu tố cụ thể. Bạn có thể thêm nhiều trình xử lý sự kiện vào một thành phần. Đôi khi nếu một phần tử DOM và trình nghe sự kiện tương ứng của nó không có cùng vòng đời, nó có thể dẫn đến rò rỉ bộ nhớ.

7. Bộ nhớ cache

Các đối tượng trong các bảng lớn, mảng và danh sách đang được sử dụng nhiều lần được lưu trữ trong bộ nhớ cache. Bộ nhớ cache tăng kích thước không giới hạn có thể dẫn đến tiêu thụ bộ nhớ cao vì nó không thể được thu gom rác. Để tránh điều này, hãy đảm bảo chỉ định giới hạn trên cho kích thước của nó.

Cách sử dụng các công cụ dành cho nhà phát triển Chrome để tìm ra rò rỉ bộ nhớ

Trong phần này, chúng tôi sẽ tìm hiểu cách sử dụng Chrome DevTools để xác định rò rỉ bộ nhớ trong mã của bạn bằng cách sử dụng ba công cụ dành cho nhà phát triển này:

  1. Chế độ xem dòng thời gian
  2. Bộ nhớ Heap
  3. Dòng thời gian phân bổ (hoặc trình phân bổ hồ sơ)

Trước tiên, hãy mở bất kỳ trình chỉnh sửa mã nào bạn chọn và tạo tài liệu HTML với mã bên dưới và mở nó trong Chrome.

<html>
<head>
   <!------ JQuery 3.3.1 ------>
   <script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
</head>
 
<body>
 
   <button id="leak-button">Start</button>
   <button id="stop-button">Stop</button>
 
   <script>
       var foo = [];
       function grow() {
           foo.push(new Array(1000000).join('foo'));
           if (running)
               setTimeout(grow, 2000);
       }
       var running = false;
 
       $('#leak-button').click(function () {
           running = true;
           grow();
       });
 
       $('#stop-button').click(function () {
           running = false;
       });
   </script>
 
</body>
</html>


Khi nhấp vào nút 'Bắt ​​đầu', nó sẽ gọi chú thích  grow() f sẽ nối thêm chuỗi dài 1000000 ký tự. Biến  foo là một biến toàn cục sẽ không phải là rác được thu thập vì nó được gọi bởi  grow() hàm đệ quy mỗi giây. Nhấp vào nút 'Dừng' sẽ thay đổi cờ đang chạy thành sai để dừng cuộc gọi chức năng đệ quy. Mỗi khi cuộc gọi chức năng kết thúc, trình thu gom rác sẽ giải phóng bộ nhớ nhưng biến  foo sẽ không được thu thập, dẫn đến kịch bản rò rỉ bộ nhớ.

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

Công cụ dành cho nhà phát triển Chrome đầu tiên mà chúng tôi sẽ đưa vào sử dụng để xác định rò rỉ bộ nhớ được gọi là 'Dòng thời gian'. Dòng thời gian là tổng quan tập trung về hoạt động của mã giúp bạn phân tích thời gian dành cho việc tải, tập lệnh, kết xuất, v.v. Bạn có thể hình dung rò rỉ bộ nhớ của mình bằng tùy chọn ghi dòng thời gian và so sánh dữ liệu sử dụng bộ nhớ trước và sau khi thu gom rác.

  • Bước 1: Mở tài liệu HTML của chúng tôi trong Chrome = và nhấn Ctrl + Shift + I để mở Công cụ dành cho nhà phát triển.
  • Bước 2: Nhấp vào tab hiệu suất để mở cửa sổ tổng quan về dòng thời gian. Nhấp vào Ctrl + E hoặc nhấp vào nút ghi để bắt đầu ghi dòng thời gian. Mở trang web của bạn và nhấp vào nút 'bắt đầu.'
  • Bước 3: Đợi trong 15 giây và tiến hành nhấp vào nút 'Dừng' trên trang web của bạn. Đợi trong 10 giây và nhấp vào biểu tượng rác ở bên phải để kích hoạt trình thu gom rác theo cách thủ công và dừng ghi âm.

LƯU Ý:  Tùy thuộc vào trình duyệt hoặc phiên bản trình duyệt của bạn, bạn có thể có một kiểu sử dụng bộ nhớ khác. Một phiên bản trình duyệt được cập nhật có thể đăng ký rò rỉ bộ nhớ không đáng kể có thể quan sát rõ ràng trong chế độ xem dòng thời gian.

Như bạn có thể thấy trong ảnh chụp màn hình ở trên, việc sử dụng bộ nhớ sẽ tăng theo thời gian. Mỗi đột biến chỉ ra khi chức năng tăng trưởng được gọi. Nhưng sau khi việc thực thi chức năng kết thúc, người thu gom rác sẽ dọn sạch phần lớn rác ngoại trừ foo biến toàn  cục. Nó tiếp tục tăng thêm bộ nhớ và thậm chí sau khi kết thúc chương trình, việc sử dụng bộ nhớ cuối cùng không giảm xuống trạng thái ban đầu.

2. Trình biên dịch bộ nhớ heap

Prof Trình tạo bộ nhớ Heap 'hiển thị phân phối bộ nhớ theo các đối tượng JavaScript và các nút DOM có liên quan. Sử dụng nó để chụp ảnh nhanh, phân tích biểu đồ bộ nhớ, so sánh dữ liệu ảnh chụp nhanh và tìm rò rỉ bộ nhớ.

  • Bước 1: Nhấn Ctrl + Shift + I để mở Chrome Dev Tools và nhấp vào bảng nhớ.
  • Bước 2: Chọn tùy chọn 'Heap Snapshot' và bấm bắt đầu.

  • Bước 3: Nhấp vào nút bắt đầu trên trang web của bạn và chọn nút chụp nhanh heap ghi ở trên cùng bên trái dưới bảng điều khiển bộ nhớ. Đợi khoảng 10-15 giây và nhấp vào nút đóng trên trang web của bạn. Đi trước và chụp ảnh heap thứ hai.

  • Bước 4: Chọn tùy chọn 'so sánh' từ trình đơn thả xuống thay vì 'tóm tắt' và tìm kiếm các phần tử DOM tách rời. Điều này sẽ giúp xác định các tham chiếu ngoài DOM.

3. Dòng thời gian phân bổ / Profiler

Trình lược tả phân bổ kết hợp thông tin ảnh chụp nhanh của trình lược tả bộ nhớ heap với theo dõi tăng dần của bảng Timeline. Công cụ này chụp ảnh heap định kỳ trong suốt quá trình ghi (thường xuyên cứ sau 50 ms!) Và một ảnh chụp nhanh cuối cùng vào cuối bản ghi. Nghiên cứu biểu đồ được tạo để phân bổ bộ nhớ đáng ngờ.

Trong các phiên bản Chrome mới hơn, tab 'Cấu hình' đã bị xóa. Bây giờ bạn có thể tìm thấy các công cụ trình phân bổ bên trong bảng bộ nhớ thay vì bảng cấu hình.

  • Bước 1: Nhấn Ctrl + Shift + I để mở Chrome Dev Tools và nhấp vào bảng nhớ.
  • Bước 2: Chọn tùy chọn 'Công cụ phân bổ trên dòng thời gian' và bấm bắt đầu.

  • Bước 3: Nhấp vào bản ghi và đợi trình hồ sơ phân bổ tự động chụp ảnh nhanh theo cách định kỳ. Phân tích biểu đồ được tạo để phân bổ bộ nhớ đáng ngờ.


Loại bỏ rò rỉ bộ nhớ

Bây giờ chúng tôi đã sử dụng thành công Chrome Developer Tools để xác định rò rỉ bộ nhớ trong mã của chúng tôi, chúng tôi cần điều chỉnh mã của mình để loại bỏ rò rỉ.

Như đã thảo luận trước đó trong phần 'Nguyên nhân rò rỉ bộ nhớ', chúng ta đã thấy các biến toàn cục không bao giờ được xử lý bởi các bộ thu gom rác, đặc biệt khi chúng được gọi một hàm đệ quy. Chúng tôi có ba cách để chúng tôi có thể sửa đổi mã của mình:

  1. Đặt biến toàn cục  foo thành null sau khi không còn cần thiết.
  2. Sử dụng 'let' thay vì 'var' để khai báo biến  foo. Hãy có một phạm vi khối không giống như var. Nó sẽ được thu gom rác.
  3. Đặt  foo biến và  grow() khai báo hàm bên trong trình xử lý sự kiện nhấp.

       var running = false;

       $('#leak-button').click(function () {
           /* Variable foo and grow function are now decalred inside the click event handler. They no longer have global scope. They now have local scope and therefore will not lead to memory leak*/
           var foo = [];

           function grow() {
               foo.push(new Array(1000000).join('foo'));
               if (running)
                   setTimeout(grow, 2000);
           }
           running = true;
           grow();
       });

       $('#stop-button').click(function () {
           running = false;
       });

Phần kết luận

Gần như không thể tránh hoàn toàn rò rỉ bộ nhớ, đặc biệt là trong các ứng dụng lớn. Một rò rỉ nhỏ sẽ không ảnh hưởng đến hiệu suất của ứng dụng theo bất kỳ cách quan trọng nào. Hơn nữa, các trình duyệt hiện đại như Chrome và Firefox được trang bị thuật toán thu gom rác tiên tiến thực hiện công việc khá tốt trong việc loại bỏ rò rỉ bộ nhớ tự động. Điều này không có nghĩa là nhà phát triển phải không biết gì về quản lý bộ nhớ hiệu quả. Thực hành mã hóa tốt đi một chặng đường dài trong việc hạn chế bất kỳ cơ hội rò rỉ nào ngay từ giai đoạn phát triển để tránh các biến chứng sau này. Sử dụng Công cụ dành cho nhà phát triển Chrome để xác định càng nhiều rò rỉ bộ nhớ càng tốt để cung cấp trải nghiệm người dùng tuyệt vời miễn phí khỏi mọi sự cố đóng băng hoặc sự cố.

Hữu ích 4 Chia sẻ Viết bình luận 0
Đã xem 6508