Memory Management In .NET

Bài viết này được dịch từ bài viết Code optimization: Memory management in .NET của tác giả S. Vikram.

Các bạn đang xem bài viết về Memory Management trong .NET từ blog của Nguyễn Thoại (http://nthoai.blogspot.com)

- Khi học lập trình C++, chúng ta biết rằng khi muốn tạo một đối tượng thì phải dùng từ khóa “new”, và khi không còn cần đến đối tượng này nữa, ta phải “delete”. Cơ chế quản lý bộ nhớ này được gọi là “reference counting”. Reference count sẽ tăng lên mỗi khi bộ nhớ được phân vùng cho một đối tượng mới, và giá trị ấy sẽ giảm đi khi đối tượng không còn sử dụng trong chương trình. Người lập trình viên trước đây phải quản lý bộ nhớ theo cách như vậy, nêú anh ta quên không delêt một object sau khi sử dụng xong, rất có thể sẽ dẫn đến một trạng thái gọi là “memory leak”.

- Trong môi trường .NET, bộ nhớ được quản lý bởi CLR. Sẽ có một thao tác gọi là “Garbage Collection” được chạy ngầm bên dưới và người lập trình .NET không cần phải nhức đầu quan tâm tới memory leaks.

The garbage collection algorithm


- Trong .NET, các object được tạo ra và lưu vào một vùng nhớ gọi là HEAP, như chúng ta biết thì HEAP là một cấu trúc hình cây. Garbage Collector sẽ thực hiện công việc của nó khi vùng nhớ Heap này full. Nó sẽ bắt đầu duyệt từ những node root, các node root này được xác định bởi JIT compliler. Nó sẽ duyệt qua chuỗi các objects và lần lượt thêm những objects nó tìm thấy vào một danh sách.

- Khi nó duyệt tới một object nào đó và phát hiện ra object này đã được thêm vào danh sách rồi, nó sẽ dừng lại. Bằng cách này, Garbage Collector sẽ duyệt qua tất cả các object được link từ những objects root của chương trình. Khi quá trình duyệt qua các object này kết thúc, danh sách của nó chỉ chứa những objects có giá trị sử dụng, những object còn lại trong Heap mà không có trong danh sách sẽ bị xem là vô giá trị và “CÓ THỂ” bị hủy đi. Chúng sẽ bị đánh dấu và bị “collected”


Heap

Hình 1: Cấu trúc bộ nhớ Heap trong .NET

- Trong hình 1, blocks 1 3 và 5 là những node có thể được sử dụng bởi các application roots. Blocks 2 và 4 sẽ bị đánh dấu để “collect”. Một khi quá trình collection đã xong, bộ nhớ của .NET được dọn dẹp và vác vùng nhớ rời rạc được dồn lại cho liên tục vì các vùng nhớ bị chiếm của các objects không cần thiết đã được thu hồi.

- Tuy nhiên, quá trình trên sẽ chạy một cách định kì chứ không phải ngay khi một object nào đó “goes out of scope”. Chỉ khi nào vùng nhớ trống bị thu hẹp đến một mức độ nào đó thì quá trình Garbage Collection mới được gọi. Điều trở ngại duy nhất của phương pháp này là nó không cho phép người lập trình xác định được chính xác thời điểm object bị hủy khỏi bộ nhớ.

Circular references


- Truy xuất vòng là trạng thái mà hai objects refer lẫn nhau. Giả sử rằng ta có một lớp A refer to lớp B. Nếu lớp B cũng refer đến lớp A thì chúng ta có một truy xuất vòng. Chúng ta có thể nghĩ rằng nếu vậy thì các object của 2 lớp này sẽ không bao giờ bị xóa khỏi bộ nhớ nhưng thực ra Garbage Collector sẽ xóa mọi object nào mà nó không duyệt tới được từ các node root.
Generations

- Thuật toán của quá trình Garbage Collection được tối ưu hóa cao. Như đã nói ở trên, Garbage Collector sẽ tạo một danh sách những objects có ích và theo dõi sự reference đến chúng. Nó chia danh sách này thành các danh dách con gọi là các Generations. CLR chia Heap ra 3 generations. Những objects mới được tạo ra được cho vào generation 0. Khi những references đến các object này được giữ trong một thời gian dài, chúng sẽ vẫn tồn tại qua các lần garbage collection. Những object như vậy sẽ được “lên đời” và được chuyển vào generation 1 và generation 2. Quá trình phần loại như thế sẽ tăng performance bởi vì Garbage Collector có thể thực hiện công việc của nó trên một generation nào đó mà không cần phải duyệt cả Heap.


Generations

Hình 2: Các Generations
Các bạn đang xem bài viết về Memory Management trong .NET từ blog của Nguyễn Thoại (http://nthoai.blogspot.com)

- Thông thường, các objects có thời gian tồn tại ngắn sẽ thường xuyên được tạo ra và huỷ đi trong Generation 0. Garbage Collector sẽ thực hiện công việc của nó trong Generation 0 chỉ khi Generation 0 bị đầy. Điều này xảy ra khi cần tạo một object mới mà không còn đủ bộ nhớ cho nó. Nếu như bộ nhớ thu hồi được đủ để tạo ra object này thì Garbage Collector sẽ không cần kiểm tra đến các Generation khác.

The Dispose design pattern: IDisposable, Dispose, and Finalize


- Common Language Runtime không thể clean up những resources như database connection, window handles và file handles. Vì thế, trách nhiệm clean up chúng thuộc về người lập trình. Những resources kể trên được gọi là unmanaged resources. Quá trình clean up những resources này có thể được thực hiện trong hàm Finalize. Finalize method được implement như là một destructor của ngôn ngữ C# nhưng rõ rang trong .NET không hề có khái niệm gọi là destructor, Finalize method chỉ tương tự như destructor mà thôi. Quá trình gọi Finalize method được điều khiển bởi Garbage Collector.

- Thường thường, khi lập trình ta vẫn muốn hủy ngay những unmanaged resources này. Khi mở file để đọc, ta muốn đóng ngay file handle khi đọc xong. Để làm được như vậy, .NET hỗ trợ chúng ta một thứ gọi là dispose design pattern.
Những objects muốn cleanup các unmanaged resource sẽ implement interface IDisposable. Interface này có method Dispose và chúng ta sẽ viết những thứ cần viết trong hàm này.

- Nếu như bạn chắc chắn là đã clean up mọi thứ trong hàm Dispose thì rõ ràng không cần thiết để Garbage Collector thực hiện clean up lần. Vì thế, trong hàm Dispose nên gọi method GC.SuppressFinalize() để Garbage Collector bỏ qua nó khi thực thi Finalization.
Chúng ta nên viết cả Finalize cũng như Dispose cho một object khi mà cần cleanup các unmanaged resources. Trong đó, hàm Finalize sẽ đóng vai trò backup cho hàm Dispose khi Dispose không được gọi. Và vì thế Garbage Collector có thể thực hiện Finalization trên object này để clean up các resources mà đáng lẽ phải được clean up trong hàm Dispose.


Generations

Hình 3: Dispose design pattern
Các bạn đang xem bài viết về Memory Management trong .NET từ blog của Nguyễn Thoại (http://nthoai.blogspot.com)

Weak references


- .NET framework còn có một feature khác khá hấp dẫn mà ta có thể sử dụng để implement các kĩ thuật caches. Đó là kĩ thuật weak reference được implement bởi .NET, lớp System.WeakReference. Như tên gọi của nó thì chức năng này cho phép ta reference tới một object nhưng có một đặc điểm là object này vẫn có thể được collect bởi “garbage collector”. Kĩ thuật cache của ASP.NET sử dụng references, nếu bộ nhớ bị sử dụng quá cao, cache sẽ được clean up.

Forcing Garbage Collection


- .NET framework đã built sẵn lớp System.GC giúp người lập trình có thể thực hiện một số can thiệp vào Garbage Collector. Garbage Collection có thể được thực hiện ngay khi chúng ta call method GC.Collect. Người ta khuyên rằng không nên lạm dụng chức năng này vì nó sẽ làm chậm hệ thống. Có một điểm cần lưu ý là khi garbage collector run, nó sẽ tạm thời treo tất cả các thread đang chạy. Hàm GC.Collect không nên được đặt ở những nơi được gọi thường xuyên như các vòng lặp, làm như vậy sẽ ảnh hưởng nặng nề đến tốc độ của chương trình.

Kết luận:


- Ngoài những điều kể trên, .NET framework còn có 2 version của cùng một CLR tùy vào máy đang chạy là server với multi-processor hay là máy work station với 1 processor. Phiên bản server build của CLR được build để tận dụng lợi thế multi-processor, nhờ đó garbage collection có thể được thực hiện song song với các quá trình khác. Trên một máy chỉ có 1 processor như các workstation, chỉ có thể load bản workstation build của CLR.

- Khi hiểu được vài trò của Dispose Design Pattern, lập trình viên .NET sẽ biết được khi nào nên implement interface IDisposable để cleanup các unmanaged resources. Và với sự hỗ trợ của Garbage Collection, ta sẽ không cần quan tâm đến việc huỷ những đối tượng .NET trong chương trình. Các dòng lệnh như objectA = null; có thể là vô nghĩa vì đã có Garbage Collector quản lý chuyện đó. Cái chúng ta cần nhớ là phải cleanup các unmanaged resource như file handles, database connection, window handles.

2 comments:

  1. Tan Hao Says:

    Mình vô tình ghé ngang block của bạn. Đọc phần GC của .net thấy hay hay. Nhưng có cái comment về sau: đọc đoạn văn dưới
    "Khi nó duyệt tới một object nào đó và phát hiện ra object này đã được thêm vào danh sách rồi, nó sẽ dừng lại. Bằng cách này, Garbage Collector sẽ duyệt qua tất cả các object được link từ những objects root của chương trình." vẫn chưa hiểu cách mà GC biết object nào còn reference tới hay không.
    Mình tên Huy chỉ là sv mới ra trường thôi nên còn non lắm. tan_hao_hcmut@yahoo.com hân hạnh được quen biết bạn.

  2. Nguyễn Thoại Says:

    Các object được tổ chức trong vùng nhớ HEAP(Dạng cây).
    Khi duyệt HEAP từ root, node nào vẫn còn duyệt qua được thì vẫn còn có giá trị lợi dụng :D

rss
 

About Me

Place I've live
Near Bossley Park, Sydney, NSW, Australia
Place I've work
  • Freelancer (from 06/2010 to present)
  • Harvey Nash (from 05/2008 to 06/2010)
  • DataDesign Vietnam (10/2005 to 04/2008)
Place I've studied
  • University of Natural Science (Bachelor of Science HoChiMinh City Vietnam From 2001 to 2005)
  • Le Hong Phong High School (HoChiMinh City Vietnam From 1997 to 2000)