Quản lý bộ nhớ trong JavaScript được thực hiện tự động và vô hình đối với chúng ta. Chúng ta tạo ra các biến nguyên thủy, các đối tượng, các hàm Tất cả những gì cần bộ nhớ.

Điều gì xảy ra khi một cái gì đó không cần thiết nữa? Làm thế nào để công cụ JavaScript phát hiện ra nó và dọn sạch nó?

1. Cách tiếp cận

Khái niệm chính về quản lý bộ nhớ trong JavaScript là Reachability(Cách tiếp cận).

Nói một cách đơn giản, các giá trị có thể truy cập được là những giá trị có thể truy cập hoặc có thể sử dụng được. Chúng được đảm bảo được lưu trữ trong bộ nhớ.

  1. Có một bộ cơ sở của các giá trị vốn có thể tiếp cận được, không thể xóa được vì những lý do rõ ràng sau. Ví dụ:
    • Các biến cục bộ và tham số của hàm hiện tại.
    • Các biến và tham số cho các hàm khác trong chuỗi các gọi hàm lồng nhau hiện tại.
    • Biến toàn cục.
    • (có một số khác, biến nội bộ đang sử dụng)
  2. Bất kỳ giá trị nào khác được coi là có thể truy cập nếu nó có thể truy cập bằng một tham chiếu hoặc bởi một chuỗi các tham chiếu. Chẳng hạn, nếu có một đối tượng trong một biến cục bộ và đối tượng đó có một thuộc tính tham chiếu đến một đối tượng khác, thì đối tượng đó được coi là có thể truy cập. Và những người mà nó tham chiếu cũng có thể truy cập.

Có một tiến trình nền trong công cụ JavaScript được gọi là trình thu gom rác(Garbage collection). Nó giám sát tất cả các đối tượng và loại bỏ những thứ đã trở nên không thể truy cập được.

2. Một ví dụ đơn giản

Đây là ví dụ đơn giản nhất:

// user has a reference to the object
let user = {
  name: "John"
};

Ở đây mũi tên mô tả một tham chiếu đối tượng. Biến toàn cục "user"tham chiếu đến đối tượng {name: "John"}(chúng ta sẽ gọi nó là John cho ngắn gọn). Thuuộc tính "name" của John lưu trữ một kiểu nguyên thủy, vì vậy nó được vẽ bên trong một đối tượng.

Nếu giá trị của userđược ghi đè, tham chiếu bị mất:

user = null;

Bây giờ John trở nên không thể truy cập. Không có cách nào để truy cập nó, không có ai tham chiếu tới nó. Trình thu gom rác sẽ làm mất dữ liệu và giải phóng bộ nhớ.

-->

3. Hai tham chiếu

Bây giờ hãy tưởng tượng chúng ta sao chép các tham chiếu từ user tới admin:

// user has a reference to the object
let user = {
  name: "John"
};

let admin = user;

Bây giờ nếu chúng ta làm như vậy:

user = null;

Sau đó, đối tượng vẫn có thể truy cập thông qua biến admin toàn cục, vì vậy nó nằm trong bộ nhớ. Nếu chúng ta gán null cho admin, thì nó có thể được gỡ bỏ.

4. Các đối tượng liên kết với nhau

Bây giờ là một ví dụ phức tạp hơn về Gia đình:

/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

function marry(man, woman) {
  woman.husband = man;
  man.wife = woman;

  return {
    father: man,
    mother: woman
  }
}

let family = marry({
  name: "John"
}, {
  name: "Ann"
});

Hàm marry với hai đối tượng bằng cách cho chúng tham chiếu với nhau và trả về một đối tượng mới chứa cả hai đối tượng.

Cấu trúc bộ nhớ như sau:

Đến bây giờ, tất cả các đối tượng có thể truy cập.

Bây giờ hãy xóa hai tham chiếu:

delete family.father;
delete family.mother.husband;

Chỉ xóa một trong hai tham chiếu này là không đủ, bởi vì tất cả các đối tượng vẫn có thể truy cập được.

Nhưng nếu chúng ta xóa cả hai, thì chúng ta có thể thấy rằng John không còn tham chiếu đến nữa:

Tham chiếu đi không quan trọng. Chỉ những tham chiếu đến mới có thể làm cho một đối tượng có thể truy cập. Vì vậy, John hiện không thể truy cập được và sẽ bị xóa khỏi bộ nhớ với tất cả dữ liệu của nó cũng không thể truy cập được.

Sau khi thu gom rác:

5. Vùng không thể truy cập được

Có thể là một vùng chứa các đối tượng được liên kết với nhau trở nên không thể truy cập được và bị xóa khỏi bộ nhớ.

Đối tượng gốc giống như trên. Sau đó:

family = null;

Hình ảnh trong bộ nhớ trở thành:

Ví dụ này cho thấy khái niệm về khả năng tiếp cận quan trọng như thế nào.

Rõ ràng là John và Ann vẫn được liên kết, cả hai đều có tham chiếu đến. Nhưng điều đó là không đủ.

Đối tượng "family" đã được hủy liên kết, không còn ai tham chiếu đến nó nữa, vì vậy toàn bộ vùng chứa của family trở nên không thể truy cập và sẽ bị xóa.

6. Thuật toán nội bộ

Thuật toán thu gom rác cơ bản được gọi là đánh dấu và quét.

Các cơ chế dọn rác sau đây được thực hiện thường xuyên:

  • Công cụ thu gom rác tìm phần đối tượng gốc và đánh dấu (nhớ) chúng.
  • Sau đó, nó ghé thăm và đánh dấu vào danh sách của 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 tham chiếu của chúng. Tất cả các đối tượng truy cập được ghi nhớ, để không truy cập cùng một đối tượng hai lần trong tương lai.
  • … và cứ thế cho đến khi mọi tham chiếu (từ gốc) có thể truy cập được và đánh dấu.
  • Tất cả các đối tượng ngoại trừ những đối tượng được đánh dấu được loại bỏ.

Ví dụ, Cấu trúc đối tượng của chúng ta trông như thế này:

Chúng ta có thể thấy rõ một vùng không thể truy cập được ở một khu vực bên phải. Bây giờ chúng ta hãy xem cách người thu gom rác đánh dấu và quét rác đối phó với nó.

Bước đầu tiên đánh dấu các đối tượng gốc rễ:

Sau đó, tham chiếu của chúng được đánh dấu:

… và tham chiếu tới chúng, cho tới khi có thể:

Bây giờ các đối tượng không thể truy cập trong quy trình được coi là không thể truy cập và sẽ bị xóa:

Chúng ta cũng có thể tưởng tượng quá trình như đổ một thùng sơn khổng lồ từ rễ, chảy qua tất cả các tham chiếu và đánh dấu tất cả các đối tượng có thể tiếp cận. Những cái không được đánh dấu sau đó được loại bỏ.

Đó là khái niệm về cách thức hoạt động của người dọn rác này(garbage collection). Các công cụ JavaScript áp dụng nhiều tối ưu hóa để làm cho nó chạy nhanh hơn và không ảnh hưởng đến việc thực thi.

Một số tối ưu hóa:

  • Tạo Một Bộ sưu tập các thế hệ – các đối tượng được chia thành hai bộ: những bộ mới và bộ đồ cũ. Nhiều vật thể xuất hiện, làm công việc của chúng và chết nhanh chóng, chúng có thể được dọn dẹp nhanh. Những người sống sót đủ lâu, trở thành người già cũ và được kiểm tra ít thường xuyên hơn.
  • Chia và Tạo Một Bộ sưu tập tăng dần – nếu có nhiều đối tượng và chúng cố gắng đi bộ và đánh dấu toàn bộ đối tượng được đặt cùng một lúc, có thể mất một thời gian và đưa ra sự chậm trễ có thể nhìn thấy trong quá trình thực thi. Vì vậy, bộ công cụ javascript cố gắng chia bộ tập hợp rác thành từng mảnh. Sau đó, các mảnh được thực hiện từng cái một, riêng biệt. Điều đó đòi hỏi một số cách bổ sung giữa chúng để theo dõi các thay đổi, nhưng chúng ta có nhiều sự chậm trễ nhỏ thay vì lớn.
  • Tạo Bộ sưu tập trong thời gian nhàn rỗi – trình thu gom rác chỉ cố gắng chạy trong khi CPU ở chế độ rảnh, để giảm hiệu ứng chậm trễ có thể xảy ra đối với việc thực thi.

Có tồn tại tối ưu hóa và một số cách khác của các thuật toán thu gom rác. Nhiều như tôi muốn mô tả chúng ở đây, tôi phải chờ, bởi vì các công cụ khác nhau thực hiện các tinh chỉnh và kỹ thuật khác nhau. Và điều thậm chí còn quan trọng hơn, mọi thứ thay đổi khi tool phát triển, vì vậy việc nghiên cứu sâu hơn, mà không có nhu cầu thực sự có lẽ không đáng. Trừ khi, tất nhiên, đó là một vấn đề quan tâm nghiên cứu sâu sắc của bạn, do đó sẽ có một số liên kết, tài liệu tham khao cho bạn dưới đây.

7. Tóm lược

Những điều chính cần biết:

  • Thu gom rác được thực hiện tự động. Chúng ta không thể ép buộc hoặc ngăn chặn nó.
  • Các đối tượng được giữ lại trong bộ nhớ trong khi chúng có thể truy cập.
  • Được tham chiếu không giống như có thể truy cập (từ gốc): một gói các đối tượng được liên kết với nhau có thể trở nên không thể truy cập được.

Tool hiện đại thực hiện các thuật toán tiên tiến của bộ sưu tập rác.

Một cuốn sách tổng hợp “The Garbage Collection Handbook: The Art of Automatic Memory Management” (R. Jones et al) bao gồm một trong số đó.

Nếu bạn đã quen thuộc với lập trình cấp thấp, thông tin chi tiết hơn về trình thu gom rác V8 có trong bài viết Tham quan về V8: Bộ sưu tập rác.

Thỉnh thoảng blog V8 cũng xuất bản các bài viết về những thay đổi trong quản lý bộ nhớ. Đương nhiên, để tìm hiểu bộ sưu tập rác, bạn nên chuẩn bị tốt hơn bằng cách tìm hiểu về nội bộ V8 nói chung và đọc blog của Vyacheslav Egorov, người từng là một trong những kỹ sư V8.

Kiến thức chuyên sâu về công cụ là tốt khi bạn cần tối ưu hóa ở mức độ thấp. Sẽ là khôn ngoan khi lập kế hoạch cho bước tiếp theo sau khi bạn quen thuộc với ngôn ngữ này.

Nguồn và Tài liệu tiếng anh tham khảo:

Tài liệu từ cafedev:

Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của cafedev để nhận được nhiều hơn nữa:

Chào thân ái và quyết thắng!

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!