Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> C#

Garbage Collection ใน C# - การจัดการหน่วยความจำอัตโนมัติ

Garbage Collector (GC) ใน .NET เป็น **Automatic Memory Manager** ที่จัดการการจัดสรร (Allocation) และการปล่อย (Release) หน่วยความจำอัตโนมัติ ทำให้นักพัฒนาไม่ต้องจัดการ Memory ด้วยตนเอง (Manual Memory Management)

หลักการทำงานพื้นฐาน

  1. Allocation: ทุกครั้งที่ new Object หรือ Boxing Value Type → จัดสรรบน Managed Heap
  2. Root Tracking: GC ตรวจสอบ Object Graph จาก Roots (Stack, Static Fields, CPU Registers)
  3. Mark Phase: ทำเครื่องหมาย Object ที่เข้าถึงได้ (Reachable)
  4. Sweep/Compact Phase: ลบ Object ที่ไม่เข้าถึงได้ (Unreachable) และ Compact Heap

Generational GC (รุ่นของ GC)

.NET ใช้ **Generational Garbage Collection** แบ่ง Heap เป็น 3 Generations:

Generation ลักษณะ ความถี่ GC
Gen 0 Object ใหม่, Short-lived (Local variables) บ่อยที่สุด (Fast, Cheap)
Gen 1 Object ที่รอดจาก Gen 0, Buffer/Intermediate ปานกลาง
Gen 2 Long-lived Objects (Static fields, Singletons, Cache) น้อยที่สุด (Expensive, Full GC)
LOH (Large Object Heap) Object ≥ 85,000 bytes (Arrays, Large Strings) Collected with Gen 2, No Compaction

ข้อดีของ Garbage Collection

  • ✅ **ไม่ต้อง Free Memory เอง** - ลด Bugs: Memory Leaks, Double Free, Use-after-free
  • ✅ **Allocation เร็วมาก** - Pointer Bump Allocation (แค่เลื่อน Pointer)
  • ✅ **Memory Safety** - ไม่มี Dangling Pointers, Buffer Overflows
  • ✅ **Compaction** - ลด Fragmentation, Improve Cache Locality
  • ✅ **Clean Objects** - Object ใหม่ได้ Zero-initialized อัตโนมัติ

GC Modes (.NET Core / .NET 5+)

Mode คำอธิบาย เหมาะสำหรับ
Workstation GC Concurrent GC, Low Latency, Single Threaded GC Client Apps (WPF, WinForms, MAUI)
Server GC Multiple Heaps (1 per Logical CPU), Parallel GC, Higher Throughput Server Apps (ASP.NET Core, Services)


  true
  
  true

IDisposable & Finalizers - จัดการ Unmanaged Resources

GC จัดการ **Managed Memory** เท่านั้น สำหรับ Unmanaged Resources (File Handles, DB Connections, Native Memory) ต้องใช้ Pattern นี้:

public class ResourceHolder : IDisposable {
    private IntPtr _unmanagedHandle;
    private bool _disposed = false;
    
    // Finalizer (Destructor) - Safety Net
    ~ResourceHolder() {
        Dispose(false);
    }
    
    // Public Dispose
    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);  // บอก GC ว่า Finalize ไม่ต้องรัน
    }
    
    // Core Cleanup Logic
    protected virtual void Dispose(bool disposing) {
        if (_disposed) return;
        
        if (disposing) {
            // Free Managed Resources (Managed Objects)
        }
        
        // Free Unmanaged Resources (เสมอ)
        if (_unmanagedHandle != IntPtr.Zero) {
            NativeMethods.CloseHandle(_unmanagedHandle);
            _unmanagedHandle = IntPtr.Zero;
        }
        
        _disposed = true;
    }
}

// ใช้งานกับ using (Auto Dispose)
using (var resource = new ResourceHolder()) {
    // ใช้งาน
}  // Dispose() เรียกอัตโนมัติ

GC API ที่น่าสนใจ

// บังคับให้ GC รัน (ไม่แนะนำให้ใช้บ่อย)
GC.Collect();
GC.Collect(2, GCCollectionMode.Forced, blocking: true);

// ตรวจสอบ Generation
int gen = GC.GetGeneration(obj);

// Memory Pressure - บอก GC ว่ามี Unmanaged Memory มาก
GC.AddMemoryPressure(bytesAllocated);
GC.RemoveMemoryPressure(bytesReleased);

// Weak Reference - อ้างอิงที่ไม่ป้องกัน GC
WeakReference weak = new WeakReference(obj);
if (weak.TryGetTarget(out var target)) {
    // Object ยังมีชีวิต
}

Best Practices

  • ใช้ using / using var สำหรับ IDisposable ทุกครั้ง
  • หลีกเลี่ยง Finalizer (~ClassName) ถ้าไม่จำเป็น - เพิ่ม Overhead ให้ GC
  • ไม่ควรเรียก GC.Collect() เอง ยกเว้นกรณีพิเศษ (Testing, Memory Profiling)
  • ใช้ Span<T>, Memory<T>, ArrayPool<T> ลด Allocation
  • ตั้งค่า ServerGarbageCollection สำหรับ Server Workloads
  • Monitor GC ด้วย dotnet-counters, dotnet-gcdump, PerfView

Memory Pressure Demo

// แสดง Gen 0/1/2 Collections
Console.WriteLine($"Gen 0: {GC.CollectionCount(0)}");
Console.WriteLine($"Gen 1: {GC.CollectionCount(1)}");
Console.WriteLine($"Gen 2: {GC.CollectionCount(2)}");
Console.WriteLine($"Allocated: {GC.GetTotalMemory(false):N0} bytes");