مدیریت Garbage Collection در ASP.NET: راهکارهای عملی کاهش فشار حافظه

سیستم Garbage Collection (GC) در ران‌تایم دات‌نت مدیریت تخصیص و آزادسازی حافظه مدیریت‌شده را به صورت خودکار انجام می‌دهد. با این حال، در وب‌سرویس‌های پرترافیک ASP.NET Core، نحوه نوشتن کد تاثیر مستقیمی بر دفعات توقف سیستم توسط GC (پدیده‌های Stop-The-World) و تاخیر کلی پاسخ‌دهی به کاربران دارد.

در این مقاله راهکارهای عملی کنترل رفتار GC و کاهش نرخ تخصیص حافظه (Allocation Rate) را بررسی می‌کنیم.

درک سازوکار زباله‌روب دات‌نت

زباله‌روب دات‌نت یک سیستم نسلی (Generational GC) است:

  • Gen 0: اشیای تازه ساخته شده با طول عمر کوتاه (مثل متغیرهای محلی توابع). پاکسازی این بخش بسیار سریع است.
  • Gen 1: اشیایی که از پاکسازی اولیه جان سالم به در برده‌اند، به عنوان لایه حائل.
  • Gen 2: اشیای با طول عمر بلند (مثل کش‌ها و سرویس‌های Singleton). پاکسازی این نسل (Full GC) سربار بالایی دارد.
  • LOH (Large Object Heap): اشیای با حجم بزرگ‌تر از ۸۵,۰۰۰ بایت مستقیما به این بخش می‌روند و باعث قطعه‌قطعه شدن حافظه (Fragmentation) می‌شوند.

راهکارهای کاهش سربار GC

۱. استفاده از انواع مقداری (Value Types) و جلوگیری از Boxing

وقتی یک Value Type (مثل int یا یک struct) به عنوان object یا اینترفیس ارسال می‌شود، عملیات Boxing رخ می‌دهد که یک شیء جدید روی Heap می‌سازد:

int num = 10;
object obj = num; // Boxing: تخصیص حافظه روی Heap

// رویکرد بهتر: استفاده از جنریک‌ها برای جلوگیری از Boxing
void LogValue<T>(T value) => Console.WriteLine(value);

۲. بهره‌گیری از Object Pooling و ArrayPool

به جای ساخت و نابودی مداوم بافرهای بایت یا اشیای پیچیده در هر درخواست HTTP، از استخر اشیا استفاده کنید:

using System.Buffers;

// استفاده از بافرهای پیش‌ساخته به جای ساخت آرایه جدید
byte[] buffer = ArrayPool<byte>.Shared.Rent(4096);
try
{
    // انجام عملیات خواندن اطلاعات
}
finally
{
    ArrayPool<byte>.Shared.Return(buffer);
}

کلاس Microsoft.Extensions.ObjectPool نیز امکان بازیافت آسان اشیای سفارشی را فراهم می‌کند:

public class ObjectPool<T> where T : class, new()
{
    private readonly Stack<T> _pool = new();

    public T GetObject()
    {
        return _pool.Count > 0 ? _pool.Pop() : new T();
    }

    public void ReleaseObject(T obj)
    {
        _pool.Push(obj);
    }
}

۳. استفاده از Span<T> و ReadOnlySpan<T>

برای دستکاری رشته‌ها، بایت‌ها و بخش‌هایی از آرایه، از اسلایس‌های بدون تخصیص حافظه با Span بهره ببرید تا از ساخت رشته‌های موقت جلوگیری شود.

۴. پیکربندی Server GC در محیط‌های سازمانی

در فایل تنظیمات پروژه یا فایل کانفیگ ران‌تایم (runtimeconfig.json)، فعال بودن Server GC برای بارهای کاری وب حیاتی است:

<PropertyGroup>
  <ServerGarbageCollection>true</ServerGarbageCollection>
  <ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
</PropertyGroup>

Server GC به ازای هر هسته پردازنده یک Heap مجزا ایجاد می‌کند که توان پردازش موازی درخواست‌ها را به حداکثر می‌رساند.

پایش و ارزیابی رفتار حافظه

با ابزارهایی مانند dotnet-counters، پروژکشن‌های Visual Studio Diagnostic Tools و BenchmarkDotNet می‌توانید خطوط تخصیص حافظه کدهای خود را دقیقاً اندازه‌گیری کنید:

dotnet-counters monitor --process-id <PID> --counters System.Runtime

جمع‌بندی

زباله‌روب دات‌نت فوق‌العاده هوشمند است، اما بهترین روش برای بهبود کارایی GC، کاهش فشار بر روی آن با کم کردن نرخ ساخت اشیای موقت است. استفاده از بافرهای مشترک، دوری از Boxing و تنظیم صحیح Server GC، تاخیر پاسخ‌دهی سرویس‌های تحت بار سنگین را به شکل محسوسی کاهش می‌دهد.