C#: Memory tự giải phóng như thế nào?

Mình có 2 class được viết như sau:

 public class Node
    {
        public string Name;
        public Node pNext;
        public Node pPrev;

        public Node(string Name)
        {
            this.Name = Name;
            pNext = null;
            pPrev = null;
        }

        public Node cloneNode()
        {
            return new Node(Name);
        }
    }

public class Bucket
    {
        private string Name;
        private Node pHead;
       
        public Bucket(string Name)
        {
            this.Name = Name;
            pHead = null;
        }
        public void addNode(Node pIn)
        {
            if (pIn == null)
                return;
            Node temp = pIn.cloneNode();
            if (pHead == null)
                pHead = temp;
            else
            {
                temp.pNext = pHead;
                pHead.pPrev = temp;
                pHead = temp;
            }
        }

        public void resetBucket()
        {
           this.pHead = null;
        }              
    }

Yêu cầu: không được khởi tạo 2 node có cùng tên. 1 node có thể nằm trong nhiều bucket khác nhau nhưng trong 1 bucket thì các node phải là duy nhất.

Ví dụ: Node A, Node B, Node C thì okay. 2 Node A thì không.
Bucket 1: Node A Node B Node C // thỏa mãn yêu cầu
Bucket 2: Node B, Node C, Node D // thỏa mãn yêu cầu
Bucket 3: Node A, Node A, Node B //không được.

Không được dùng container hay array nên các bạn đừng khuyên mình dùng mấy cái như Hashset hay Dictionary nhé. Lại phải dùng linked list rồi.

Mình không rành C# với garbage collection lắm nên sau đây là câu hỏi của mình:

Vì mỗi node có thể nằm trong nhiều bucket nên khi thêm vào bucket nên mình không thể cứ truyền thẳng node đó vào được. Nếu truyền thẳng thì nó sẽ luôn reference cái node đó. Do đó mình sẽ tạo ra 1 node từ node chủ => lí do tại sao có hàm cloneNode().
Vì gọi new trong hàm cloneNode() nên node sẽ nằm trên heap. Vậy khi mình muốn xóa tất cả các node trong bucket, với cách mình viết như trên thì khi mình gọi clearBucket() các vùng nhớ được cấp cho các Node có được giải phóng không?

Minh họa:
Tập hợp Node: NodeA, NodeB, NodeC
Bucket_1: clone_NodeA, clone_NodeB, clone_NodeC.

Khi mình set pHead trong Bucket_1 là null thì sau khi hàm clearBucket() thực thi xong (ra khỏi scope của hàm) các vùng nhớ được cấp phát cho clone_NodeA, clone_NodeB, clone_NodeC khi gọi cloneNode() trong addNode() có được tự động giải phóng không?

Bạn có thể tìm kiếm mà.

7 Likes

Tùy vào thời điểm (và địa điểm) bạn gọi hàm clear bucket. Nếu sử dụng và clear bucket ngay trong hàm thì address mà bucket đó đang giữ sẽ được reclaimed ngay.

Vì garbage collector của .NET bao gồm nhiều stages (gọi là generation), những object mới tạo sẽ được đưa vào stage 0 và có khả năng bị collected lớn nhất, sau đợt collect stage 0 (scan stage 0, lọc ra những object không còn được referenced), những object còn sống sẽ được đưa vào stage 1, 2… càng sống lâu thì càng ít bị GC duyệt.

Ý tưởng của multi-stage GC là để không phải scan 1 lúc toàn bộ objects trong chương trình, mà chỉ scan theo từng stage. Lưu ý là dù cùng là C# nhưng những implementation khác nhau có thể có behaviour khác nhau. .NET sử dụng generational mark-and-sweep GC với 3 stage, Mono chỉ có 2 stage và Unity không dùng generational GC.

6 Likes

Bạn có thể giải thích rõ chỗ “sử dụng và clear bucket ngay trong hàm” được ko? Nếu được bạn cho mình xin 1 cái ví dụ đợn giản.

1 Like

Sorry, có lẽ mình giải thích không kĩ. Ví dụ:

void UseBucket()
{
  Bucket bucket = new Bucket("Bucket_1"); // 1
  bucket.AddNode(new Node("A")); // 2
  bucket.AddNode(new Node("B")); // 3
  bucket.AddNode(new Node("C")); // 4
  ...
  bucket.ClearBucket(); // 5
  bucket.AddNode(new Node("C")); // 6
  bucket.AddNode(new Node("D")); // 7
  ...
  bucket.ClearBucket();
  ...
}

Ví dụ, memory budget của GC là 17 KiB [1], và cost của 1 Node là 4 KiB.
Sau dòng 5, memory của node A, B, C chưa bị GC lấy lại, vì chưa hết budget (còn 1 KiB trống), dù pHead đã null và bạn sẽ không reference được những node còn lại.
Sau dòng 6, memory của node A, B, C sẽ bị lấy lại, vì budget đã đầy. Heap (stage 0) của bạn sẽ chỉ còn node C.

[1] Thực tế memory budget sẽ không nhỏ như vậy, GC sẽ fine-tune kích thước stage 0 (từ ~256 KiB đến khoảng 2 - 4 - … MiB)

P/s: Mình đã bỏ qua một số corner cases để đơn giản hóa giải thích nhất có thể, nên câu trả lời này là không đầy đủ. Nếu có thời gian, bạn nên tham khảo link trong post của @SITUVN.gcd hoặc link này. Chúng sẽ cho bạn câu trả lời đầy đủ hơn.

4 Likes

Cám ơn bạn

dummy cho đủ 20

83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?