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
var user = new User(); // Gen 0C#🔹 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
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)
byte[] buffer = new byte[100_000]; // LOHC#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
public class FileLogger : IDisposable
{
private StreamWriter _writer;
public FileLogger(string path)
{
_writer = new StreamWriter(path);
}
public void Dispose()
{
_writer.Dispose();
}
}C#using Statement
using (var logger = new FileLogger("log.txt"))
{
logger.Log("Hello");
}C#5. Finalizer ve GC.SuppressFinalize
Finalizer (~ClassName)
~FileLogger()
{
Dispose(false);
}C#Doğru Disposable Pattern
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
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
Memory<byte> memory = new byte[1024];
await stream.ReadAsync(memory);C#
Span vs Memory
| Özellik | Span | Memory |
|---|---|---|
| Heap Allocation | ❌ | ✅ |
| Async Kullanım | ❌ | ✅ |
| Performans | ⭐⭐⭐⭐ | ⭐⭐⭐ |
7. Gerçek Hayat Senaryosu (Web API)
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>veArrayPool<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.

Yorum Yap