Quản lý bộ nhớ trong .NET (Garbage Collection)


Nếu bạn đã từng lập trình C/C++ thì chắc hẳn sẽ nhớ đến một trong những điểm mạnh và cũng rất phức tạp của ngôn ngữ này là con trỏ và cấp phát động. Bạn luôn phải tuân thủ theo quy tắc: cấp bao nhiêu, thu hồi bấy nhiêu. Ví dụ đoạn chương trình C++ sau cấp phát một vùng nhớ gồm 5 phần tử số nguyên như sau:

int *p = new int[5];

Và giả sử như sau khi kết thúc chương trình mà chúng ta không thu hồi vùng nhớ này thì chuyện gì sẽ xảy ra? 5 ô nhớ này sẽ cứ tồn tại trong bộ nhớ trong khi các chương trình khác không thể sử dụng được vùng nhớ này (hay còn gọi là rò rỉ bộ nhớ – memory leak). Nếu như điều này diễn ra nhiều lần thì chúng ta sẽ dần dần không còn đủ bộ nhớ để sử dụng. Vì thế, trong C++, yêu cầu bắt buộc là sau khi sử dụng xong vùng nhớ đã cấp phát thì phải thu hồi. Để thu hồi lại vùng nhớ chúng ta đã cấp phát trong ví dụ trên thì ta sử dụng hàm delete trong C++:

delete[] p;

Ở đây, vì thu hồi lại vùng nhớ của một mảng gồm nhiều phần tử nên chúng ta phải sử dụng thêm dấu ngoặc vuông để cho chương trình biết rằng p là địa chỉ đầu của một mảng liên tiếp nhiều phần tử.

Trong .NET, khái niệm con trỏ bị hạn chế sử dụng (nếu muốn sử dụng thì phải đánh dấu đoạn mã là unsafe). Tuy nhiên, thực chất khi chúng ta tạo một đối tượng của lớp (nói rộng hơn là một kiểu tham chiếu) thì quá trình cấp phát bộ nhớ cũng tương tự như cấp phát động cho con trỏ trong C++. Xét khai báo của một class sau:

class MyClass
{
      int x;
      int y;
}

Sau đó, tại một nơi nào đó trong chương trình, chúng ta tạo một đối tượng của lớp này như sau:

MyClass c = new MyClass();

Dòng lệnh trên sẽ tạo ra một biến c trong bộ nhớ và biến này sẽ lưu trữ địa chỉ của vùng nhớ thật sự chứa dữ liệu của đối tượng lớp đó. Hình sau cho chúng ta thấy biến c không hề chứa dữ liệu mà nó sẽ tham chiếu đến một vùng nhớ thực sự chứa dữ liệu.

image

Nhưng chúng ta biết rằng .NET không cung cấp cho chúng ta từ khóa delete để thu hồi lại vùng nhớ đã được cấp, vậy “vùng nhớ đó thật sự được thu hồi như thế nào?”

Trong .NET (cũng như Java) có một khái niệm là Bộ dọn rác (Garbage Collector). Đây là một tiến trình đặc biệt có nhiệm vụ duyệt qua các vùng nhớ đã được cấp phát và kiểm tra xem vùng nhớ nào không còn được sử dụng nữa (không còn tham chiếu tới nó nữa) thì sẽ thực hiện thu hồi một cách tự động để có thể cấp phát cho các yêu cầu tiếp theo. Vấn đề chúng ta cần biết ở đây là, “làm thế nào Garbage Collector có thể biết được rằng vùng nhớ đó không còn được sử dụng nữa để mà thu hồi?”

Chúng ta có thể hình dung được công việc của Garbage Collector như sau: Khi chương trình khởi chạy thì một vùng nhớ liên tục còn trống sẽ được dành riêng để cấp phát cho các biến trong chương trình (vùng nhớ này được gọi là managed-heap). Khi chúng ta dùng toán tử new để tạo một đối tượng mới thì chương trình sẽ kiểm tra xem vùng nhớ này còn đủ để cấp phát hay không, nếu không đủ thì GC sẽ được khởi động. Bước đầu tiên mà GC thực hiện là tạm dừng chương trình và thực hiện việc duyệt để đánh dấu tất cả những vùng nhớ đang được sử dụng với khởi đầu tại một điểm nào đó đã biết trước (hay được gọi là điểm gốc).

Figure 2 Allocated Objects in Heap

Như hình trên, chúng ta thấy rằng chương trình này có một số điểm gốc tham chiếu tới các đối tượng F, D, C và A. GC sẽ bắt đầu tại 1 điểm gốc nào đó tham chiếu tới D, GC sẽ đánh dấu D là “còn sống”. Tiếp đó GC thấy rằng D tham chiếu tới H nên cũng sẽ đánh dấu H là “còn sống”. Cứ tiếp tục như vậy, GC sẽ duyệt qua tất cả các đối tượng đang được tham chiếu bởi một đối tượng nào đó trong chương trình.

Tất cả những đối tượng mà không được GC đánh dấu là “còn sống” thì sẽ được xem là rác và được giải phóng ngay lập tức. Liền sau đó, GC phải thực hiện công việc di chuyển các khối bộ nhớ rời rạc, phân mảnh nằm gần lại với nhau để tạo thành một vùng liên tục. Như vậy, từ hình trên ta có thể suy ra được vùng nhớ sau khi đã được thu dọn và sắp xếp nhờ GC như sau

Figure 3 Managed Heap after Collection

Kết luận: Quá trình thực hiện dọn dẹp bộ nhớ của Garbage Collector thật sự khiến cho chương trình của chúng ta sẽ chạy chậm hơn so với các chương trình viết bằng C/C++. Tuy nhiên, năng suất mà chúng ta đạt được là rất đáng kể bởi vì chúng ta không phải tập trung giải quyết những công việc đòi hỏi sự tỉ mỉ, cẩn thận với ngôn ngữ lập trình mà chỉ cần tập trung vào giải quyết các vấn đề của khách hàng đưa ra.

Tác giả: xuanchien

Tran Xuan Chien. Japan Advanced Institute of Science and Technology - Japan. Senior Developer - NUS Technology.

4 thoughts on “Quản lý bộ nhớ trong .NET (Garbage Collection)”

  1. Chào bạn!
    bạn cho mình hỏi rõ thêm 1 chút nữa nha:
    mình thấy 1 đoạn ghi là “Vấn đề chúng ta cần biết ở đây là, “làm thế nào Garbage Collector có thể biết được rằng vùng nhớ đó không còn được sử dụng nữa để mà thu hồi?”
    bạn cho mình hỏi 2 vấn đề:
    1./- Trường hợp mình có 1 biến khai báo Global : private int[] arr; vậy khi sau khi mình muốn bộ GC thu hồi vùng nhớ thì làm sao để đánh dấu cho GC biết là vùng nhớ đó không còn được sử dụng nữa!
    2./- Trong 1 hàm bình thường mình có cần đánh dấu không sử dụng nữa khi kết thúc hàm hay ko? hay là khi kết thúc hàm thì GC tự động thu hồi vùng nhớ
    Cảm ơn!

    1. Thật ra bạn không cần thiết phải đánh dấu cho GC biết để thu hồi vùng nhớ cho các trường hợp này vì đây đều là các trường hợp bình thường và GC sẽ tự động thu hồi. Chỉ một số trường hợp hiếm hoi khi chúng ta có những resources như một biến trỏ đến file đang mở, hoặc 1 vùng nhớ mình cố tình cấp phát (unsafe) thì mới cần đánh dấu để GC biết mà thu hồi. Bạn tìm hiểu thêm về “Unmanaged resources” thì sẽ rõ hơn

Gửi phản hồi

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Log Out / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Log Out / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Log Out / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Log Out / Thay đổi )

Connecting to %s