Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

CLR & Bộ nhớ

Garbage Collection (GC)

GC hoạt động như thế nào?

Garbage Collection là quản lý bộ nhớ tự động trong .NET, hoạt động theo cơ chế:

// Khi không còn reference, object sẽ được GC thu hồi
public void CreateObject()
{
    var obj = new MyClass(); // Allocate trên Heap
    // Khi method kết thúc, obj không còn reference
    // GC sẽ thu hồi bộ nhớ
}

GC Cycle

  1. Allocation: Khi object mới được tạo, nó được allocate trên Heap
  2. Mark Phase: GC đánh dấu tất cả objects có reference (root objects)
  3. Sweep Phase: Xóa các objects không có reference
  4. Compact Phase: Di chuyển các objects còn lại để giảm fragmentation

Generations

┌─────────────────────────────────────────────────────────────┐
│                        LARGE OBJECT HEAP                    │
│                    (>85KB objects)                          │
├─────────────────────────────────────────────────────────────┤
│  Generation 2 (Long-lived)    │ Gen 1 (Intermediate)       │
│  ┌─────────────────────────┐   │ ┌───────────────────────┐  │
│  │ Static variables        │   │ │ Objects that survived│  │
│  │ Singleton services      │   │ │ Gen 1 collection     │  │
│  └─────────────────────────┘   │ └───────────────────────┘  │
├─────────────────────────────────────────────────────────────┤
│  Generation 0 (Short-lived)                                │
│  ┌───────────────────────────────────────────────────────┐  │
│  │ Local variables, temporary objects                    │  │
│  │ Created and destroyed frequently                      │  │
│  └───────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
GenerationMô tảTần suất GC
Gen 0Short-lived objects (local variables)Thường xuyên nhất
Gen 1Intermediate objectsÍt hơn Gen 0
Gen 2Long-lived objects (static, singletons)Ít nhất

Tối ưu để giảm áp lực lên GC

1. Sử dụng StringBuilder thay vì String

// ❌ Tạo nhiều string objects
string result = "";
for (int i = 0; i < 1000; i++)
{
    result += i.ToString(); // Mỗi lần tạo new string
}

// ✅ Sử dụng StringBuilder
var sb = new StringBuilder();
for (int i = 0; i < 1000; i++)
{
    sb.Append(i);
}
string result = sb.ToString();

2. Sử dụng struct khi phù hợp

// Value type - lưu trên Stack
public struct Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

// Reference type - lưu trên Heap
public class PointClass
{
    public int X { get; set; }
    public int int Y { get; set; }
}

3. Tránh boxing/unboxing

// ❌ Boxing - chuyển value type sang object
int i = 10;
object o = i; // Boxing

// ✅ Tránh boxing
int i = 10;
long l = i; // Không boxing

4. Sử dụng Span và Memory

// Tránh allocation khi xử lý arrays
Span<int> span = stackalloc int[100]; // Stack allocation
span[0] = 42;

// Hoặc slice without allocation
var slice = span.Slice(0, 10);

5. Dispose objects sử dụng using

// ✅ Tự động gọi Dispose
using var file = new StreamReader("file.txt");
var content = file.ReadToEnd();

// ✅ Hoặc using statement truyền thống
using (var file = new StreamReader("file.txt"))
{
    var content = file.ReadToEnd();
}

6. GC.Collect() - Khi nào nên dùng?

// ❌ Không nên gọi thủ công trong production
GC.Collect();

// ✅ Có thể dùng sau khi giải phóng large objects
public void Cleanup()
{
    largeObject = null;
    GC.Collect();
    GC.WaitForPendingFinalizers();
}

Value Types vs Reference Types

So sánh cách lưu trữ

┌─────────────────────────────────────────────────────────────┐
│                      STACK                │      HEAP       │
├────────────────────────────────────────────┼─────────────────┤
│                                            │                 │
│  int x = 10;           │ 10                 │                 │
│  int y = x;            │ 10  (copy)         │                 │
│                                            │                 │
│  var p1 = new Point(); │ 0x1234 (ref)──────►│ Point { x: 0 }  │
│  var p2 = p1;          │ 0x1234 (ref)──────►│ Point { x: 0 }  │
│                                            │                 │
└────────────────────────────────────────────┴─────────────────┘

Khi nào sử dụng struct?

  • Kích thước nhỏ (<16 bytes)
  • Immutable (không thay đổi sau khi tạo)
  • Không cần inheritance
  • Tạo và hủy thường xuyên (trong loops)
// ✅ Nên dùng struct
public readonly struct Point
{
    public double X { get; }
    public double Y { get; }
    
    public Point(double x, double y) => (X, Y) = (x, y);
}

// ❌ Không nên dùng struct
public struct LargeObject
{
    public byte[] Data { get; set; } // Array - vẫn allocate trên Heap!
}

Performance Impact

OperationValue TypeReference Type
AllocationStack (nhanh)Heap (chậm hơn)
CopyCopy toàn bộCopy reference
PassingCopy toàn bộCopy reference (4/8 bytes)
GC PressureKhông

ref, out, in modifiers

ref - Pass by Reference

public void Swap(ref int a, ref int b)
{
    int temp = a;
    a = b;
    b = temp;
}

int x = 5, y = 10;
Swap(ref x, ref y);
// x = 10, y = 5
  • Caller phải khởi tạo biến trước khi pass
  • Có thể đọc và ghi

out - Output Parameter

public bool TryParse(string input, out int result)
{
    if (int.TryParse(input, out result))
    {
        return true;
    }
    result = 0; // Phải gán trước khi return
    return false;
}

if (TryParse("42", out int number))
{
    Console.WriteLine(number);
}
  • Caller KHÔNG cần khởi tạo
  • Phải gán giá trị trước khi method return
  • Thường dùng cho “output” values

in - Read-only Reference

public void Print(in Point point)
{
    // point.X = 10; // ❌ Lỗi - readonly
    Console.WriteLine(point.X); // ✅ Đọc được
}
  • Không thể modify giá trị
  • Tránh copying khi passing large structs
  • Cải thiện performance cho large structs

So sánh

ModifierCaller initializeCan modifyUse case
ref✅ Yes✅ YesIn-place modification
out❌ No✅ YesReturn multiple values
in✅ Yes❌ NoRead-only, performance