Bilgisayar Genel

C#’ta Memory Management, GC Nasıl Çalışır?

Tarafından yazılmıştır Halil Durmuş

Kurumsal ve uzun süre çalışan (.NET Web API, Worker Service, CRM entegrasyonları gibi) uygulamalarda memory management, performans ve stabilite açısından kritik bir konudur. Bu yazıda .NET Garbage Collector (GC) mimarisini, nesil (Generation) mantığını, Large Object Heap’i, IDisposable desenini ve modern bellek dostu yapılar olan Span<T> / Memory<T> kullanımını okuyup analiz edilebilir bir seviyede ele alacağız.

1. Garbage Collector (GC) Nedir, Ne Yapar?

.NET’te bellek yönetimi büyük ölçüde Garbage Collector tarafından otomatik olarak yapılır.

GC’nin temel görevleri:

  • Heap üzerinde tahsis edilen nesneleri takip etmek
  • Artık erişilmeyen (unreachable) nesneleri temizlemek
  • Belleği compact ederek fragmentation’ı azaltmak

⚠️ Önemli: GC bir memory leak önleyici değildir. Yanlış referans yönetimi hâlâ bellek sızıntısına yol açabilir.

2. Generation (Gen 0 – Gen 1 – Gen 2) Mantığı

GC, performans için nesneleri yaşam sürelerine göre sınıflandırır.

🔹 Gen 0
  • Yeni oluşturulan nesneler
  • Çok kısa ömürlüdür
  • En sık temizlenen nesildir
C#
var user = new User(); // Gen 0
C#

🔹 Gen 1
  • Gen 0’dan sağ çıkan nesneler
  • Ara geçiş nesilleri
🔹 Gen 2
  • Uzun ömürlü nesneler (cache, singleton, static referanslar)
  • Temizlemesi pahalıdır
C#
static readonly List<string> _cache = new(); // Gen 2 adayı
C#

📌 GC Kuralı:

Çoğu nesne genç ölür → Bu yüzden Gen 0 sık, Gen 2 nadir toplanır.

3. Large Object Heap (LOH)

LOH Nedir?
  • 85.000 byte üzerindeki nesneler LOH’a gider
  • Gen 2 ile birlikte toplanır
  • Varsayılan olarak compact edilmez (fragmentation riski)
C#
byte[] buffer = new byte[100_000]; // LOH
C#

LOH Problemleri
  • Fragmentation
  • Uzun GC pause süreleri

4. IDisposable ve Finalizer Mantığı

IDisposable Ne Zaman Gerekli?

Managed olmayan kaynaklar varsa:

  • File handle
  • DB connection
  • Stream
  • Socket
C#
public class FileLogger : IDisposable
{
private StreamWriter _writer;


public FileLogger(string path)
{
_writer = new StreamWriter(path);
}


public void Dispose()
{
_writer.Dispose();
}
}
C#

using Statement

C#
using (var logger = new FileLogger("log.txt"))
{
logger.Log("Hello");
}
C#

5. Finalizer ve GC.SuppressFinalize

Finalizer (~ClassName)

C#
~FileLogger()
{
Dispose(false);
}
C#

Doğru Disposable Pattern
C#
public class ResourceHolder : IDisposable
{
  private bool _disposed;
  
  public void Dispose()
  {
    Dispose(true);
    GC.SuppressFinalize(this);
  }


  protected virtual void Dispose(bool disposing)
  {
    if (_disposed) return;
    if (disposing)
    {
      // managed resources
    }


    // unmanaged resources
    _disposed = true;
  }
}
C#

📌 GC.SuppressFinalize çağrılmazsa nesne iki kez GC sürecine girer.

6. Span ve Memory / Modern Bellek Dostu Yapılar

Neden Gerekli?
  • Allocation azaltmak
  • High-performance senaryolar
  • Copy yerine slice mantığı

Span
  • Stack üzerinde çalışır
  • Heap allocation yapmaz
  • Async/await ile kullanılamaz
C#
Span<int> numbers = stackalloc int[5];
numbers[0] = 10;
void Process(Span<byte> buffer)
{
  // slicing
  var header = buffer[..4];
}
C#

Memory
  • Heap üzerinde çalışır
  • Async uyumludur
  • Daha güvenli ama biraz daha maliyetlidir
C#
Memory<byte> memory = new byte[1024];
await stream.ReadAsync(memory);
C#

Span vs Memory
ÖzellikSpanMemory
Heap Allocation
Async Kullanım
Performans⭐⭐⭐⭐⭐⭐⭐

7. Gerçek Hayat Senaryosu (Web API)

C#
public async Task<IActionResult> Upload(IFormFile file)
{
  using var stream = file.OpenReadStream();
  byte[] buffer = ArrayPool<byte>.Shared.Rent(81920);

  try
  {
    int read;
    while ((read = await stream.ReadAsync(buffer)) > 0)
    {
      // process
    }
  }
  finally
  {
    ArrayPool<byte>.Shared.Return(buffer);
  }
  return Ok();
}
C#

📌 Bu yapı:

  • LOH kullanımını azaltır
  • GC baskısını düşürür

8. Özet ve Best Practices

  • Kısa ömürlü nesneleri Gen 0’da tut
  • LOH allocation’larından kaçın
  • IDisposable kullanmayı ihmal etme
  • Finalizer’ı zorunlu değilse yazma
  • High-performance alanlarda Span<T> ve ArrayPool<T> kullan

GC’yi “nasıl çalıştığını bilen” bir geliştirici olmak, kurumsal projelerde seni bir adım öne taşır. Özellikle yüksek trafikli CRM, entegrasyon ve API projelerinde doğru memory yönetimi, performans sorunlarının %80’ini daha oluşmadan engeller.

Yazar hakkında

Halil Durmuş

1996 yılının Mart ayında Trabzon’da dünyaya geldim. Atatürk Üniversitesi, Bilgisayar Mühendisliği mezunuyum. Web sitemde ilgimi çeken konuları araştırarak yazılar paylaşıyorum.

Yorum Yap