Windows’ta Bellek Sızıntısını Tespit ve Giderme
Üretim ortamında bir Windows sunucusu yönetiyorsunuz, her şey yolunda gidiyor, derken bir sabah monitoring sisteminiz alarm veriyor: bellek kullanımı %95’i geçmiş. Sunucuyu yeniden başlatıyorsunuz, birkaç gün sakin, sonra aynı tablo. Bu döngü size tanıdık geliyorsa, büyük ihtimalle bir bellek sızıntısıyla karşı karşıyasınızdır. Ben bu durumu onlarca kez yaşadım ve her seferinde farklı bir şeyin sorumlu olduğunu gördüm; IIS uygulama havuzu, .NET servis, üçüncü parti agent, hatta Windows’un kendi bileşenleri bile bazen suçlu çıkıyordu. Bu yazıda size hem tespit hem de giderme sürecini, gerçek araçlarla ve gerçek komutlarla anlatacağım.
Bellek Sızıntısı Nedir, Ne Değildir?
Önce şunu netleştirelim: Windows’ta yüksek bellek kullanımı her zaman sızıntı değildir. Windows belleği agresif biçimde önbelekler; “Standby” listesindeki bellek dolu görünse de sisteme ihtiyaç duyduğunda hemen geri verilir. Task Manager’da %80 bellek görmek tek başına alarm nedeni değildir.
Gerçek sızıntının belirtileri şunlardır:
- Bir sürecin bellek kullanımı sürekli ve düzenli olarak artıyor, hiç düşmüyor
- Sunucu yeniden başlatma yapılmadan bellek serbest bırakılmıyor
- Uygulama zamanla yavaşlıyor, yanıt vermez hale geliyor
- Sistem event log’larında “low memory” uyarıları beliriyor
- Sayfalama dosyası (pagefile) sürekli doluyor
Bu farkı anlamadan müdahaleye kalkışırsanız hem zaman kaybedersiniz hem de yanlış yere bakarsınız.
İlk Adım: Durumu Hızlıca Tespit Etmek
Bir problemi görünce ilk refleks Task Manager’ı açmak olur, ama yeterli değildir. Hızlı bir tablo çıkarmak için PowerShell’den başlayalım.
# En fazla bellek kullanan 15 süreci listele (MB cinsinden)
Get-Process | Sort-Object WorkingSet64 -Descending |
Select-Object -First 15 Name, Id,
@{N='WorkingSet_MB';E={[math]::Round($_.WorkingSet64/1MB,2)}},
@{N='PrivateMemory_MB';E={[math]::Round($_.PrivateMemorySize64/1MB,2)}},
@{N='VirtualMemory_MB';E={[math]::Round($_.VirtualMemorySize64/1MB,2)}} |
Format-Table -AutoSize
Burada dikkat etmeniz gereken sütun PrivateMemory_MB‘dir. Bu değer, sürecin başka hiçbir süreçle paylaşmadığı, salt kendisine ait bellek miktarını gösterir. Eğer bu değer sürekli artıyorsa, elimizde ciddi bir ipucu var demektir.
Anlık görüntü yetmez; trendi görmek için zaman içinde örnekleme yapmanız gerekir:
# Belirli bir sürecin bellek kullanımını 60 saniye boyunca, 5'er saniyede bir örnekle
$processName = "w3wp" # İzlemek istediğiniz süreç adı
$samples = @()
1..12 | ForEach-Object {
$proc = Get-Process -Name $processName -ErrorAction SilentlyContinue
if ($proc) {
$samples += [PSCustomObject]@{
Zaman = Get-Date -Format "HH:mm:ss"
PID = $proc.Id
WS_MB = [math]::Round($proc.WorkingSet64/1MB, 2)
PrivMem_MB = [math]::Round($proc.PrivateMemorySize64/1MB, 2)
}
}
Start-Sleep -Seconds 5
}
$samples | Format-Table -AutoSize
Bu çıktıda PrivMem_MB değeri her örnekte monoton biçimde artıyorsa, artık “sızıntı var” diyebilirsiniz. Düzensiz dalgalanma normal yük değişimidir.
Performance Monitor ile Derinlemesine İzleme
Task Manager ve PowerShell anlık fotoğraf verir, ama uzun süreli izleme için Performance Monitor (PerfMon) ayrı bir yer tutar. Özellikle şu sayaçları takip edin:
- MemoryAvailable MBytes: Sistemde gerçekte kullanılabilir bellek
- MemoryPages/sec: Yüksekse bellek baskısı var demektir
- MemoryPool Nonpaged Bytes: Kernel pool sızıntıları için kritik
- ProcessPrivate Bytes(_Total): Tüm süreçlerin toplam özel bellek kullanımı
- ProcessWorking Set(hedef_süreç): Belirli bir sürecin anlık bellek ayak izi
PerfMon yerine komut satırında veri toplamak isterseniz, logman kullanabilirsiniz:
# Bellek ile ilgili sayaçları 1 saat boyunca, 30 saniyede bir kaydet
logman create counter MemoryLeak -c "Memory*" "Process(*)Private Bytes" "Process(*)Working Set" -si 30 -f csv -o C:Logsmemleak_%computername%.csv -cnf 01:00:00
# Kaydı başlat
logman start MemoryLeak
# Bir saat sonra durdur ve veriyi incele
logman stop MemoryLeak
Toplanan CSV’yi Excel’e atıp süreç bazında grafik çizdiğinizde sızıntının eğrisini net görebilirsiniz. Hangi süreç düz çizgi değil de yukarı tırmanan bir eğri çiziyorsa, o bizim hedefimiz.
Kernel Pool Sızıntılarını Tespit Etmek
Bazen sorun kullanıcı alanı süreçlerinde değil, kernel pool’dadır. Driver’lar ve bazı sistem bileşenleri NonPaged Pool’dan bellek ayırır ve düzgün serbest bırakmazlarsa sistem bu belleksiz kalır. Bu durum çok daha tehlikelidir çünkü sistem kararsızlaşır ve BSOD’a bile yol açabilir.
# Kernel pool kullanımını göster
# Poolmon.exe, Windows Driver Kit (WDK) ile gelir
# Alternatif olarak PowerShell ile anlık durumu kontrol edelim
# Pool bilgisini al
$os = Get-WmiObject Win32_OperatingSystem
$pools = Get-Counter 'MemoryPool Nonpaged Bytes', 'MemoryPool Paged Bytes'
$pools.CounterSamples | ForEach-Object {
[PSCustomObject]@{
Sayac = $_.Path.Split('')[-1]
Deger_MB = [math]::Round($_.CookedValue/1MB, 2)
}
} | Format-Table -AutoSize
# İşletim sistemi bellek özetini al
[PSCustomObject]@{
TotalVisibleMem_MB = [math]::Round($os.TotalVisibleMemorySize/1KB, 2)
FreePhysMem_MB = [math]::Round($os.FreePhysicalMemory/1KB, 2)
TotalVirtualMem_MB = [math]::Round($os.TotalVirtualMemorySize/1KB, 2)
FreeVirtualMem_MB = [math]::Round($os.FreeVirtualMemory/1KB, 2)
}
Eğer NonPaged Pool değeri zamanla 500 MB’ı geçiyorsa ve yukarı tırmanıyorsa, kernel seviyesinde bir driver sızıntısı olduğunu düşünmeye başlayın. Bu durumda poolmon.exe ile hangi tag’in en fazla bellek tuttuğunu bulmak gerekir; WDK kurulu sistemlerde şu şekilde çalıştırabilirsiniz:
# Poolmon'u run et ve NonPaged Pool büyüklüğüne göre sırala
# (WDK kurulu sistemlerde C:Program Files (x86)Windows Kits10Tools... altındadır)
poolmon.exe -n -b
Çıktıda “Tag” sütunundaki koda bakın; Microsoft’un online veritabanında ya da findstr ile C:WindowsSystem32drivers*.sys içinde bu tag’e ait driver’ı bulabilirsiniz.
IIS ve .NET Uygulamalarında Sızıntı Tespiti
Üretim ortamında en sık karşılaştığım senaryo IIS üzerinde çalışan .NET uygulamalarıdır. w3wp.exe sürecinin belleği her talep sonrası biraz artıp hiç azalmıyorsa, büyük ihtimalle uygulama kodunda dispose edilmeyen nesneler, kapatılmayan connection’lar veya static koleksiyonlara sürekli ekleme yapılması söz konusudur.
Hızlı teşhis için önce hangi uygulama havuzunun sorunlu olduğunu bulalım:
# Tüm w3wp süreçlerini uygulama havuzu adıyla birlikte listele
# (IIS kurulu sunucularda)
Import-Module WebAdministration
$appPools = Get-WebConfiguration system.applicationHost/applicationPools/add
foreach ($pool in $appPools) {
$procs = Get-WmiObject Win32_Process -Filter "Name='w3wp.exe'" |
Where-Object { $_.CommandLine -like "*$($pool.Name)*" }
if ($procs) {
foreach ($proc in $procs) {
[PSCustomObject]@{
AppPool = $pool.Name
PID = $proc.ProcessId
Memory_MB = [math]::Round(
(Get-Process -Id $proc.ProcessId).WorkingSet64/1MB, 2)
StartTime = $proc.CreationDate
}
}
}
}
Hangi w3wp’nin şiştiğini bulduktan sonra, o sürecin dump’ını almak gerekir. Bunu yaparken uygulamayı durdurmazsınız; procdump aracı (Sysinternals) non-intrusive biçimde çalışır:
# ProcDump ile belirli bir w3wp sürecinin memory dump'ını al
# (PID'i önceki adımda tespit ettiğiniz değerle değiştirin)
procdump.exe -ma 1234 C:Dumpsw3wp_1234.dmp
# Alternatif: Bellek belirli bir eşiği geçince otomatik dump al
procdump.exe -ma -m 2048 -c 85 w3wp C:Dumps
# -m 2048: Working set 2 GB geçince dump al
# -c 85: CPU %85'i geçerse de dump al
Dump’ı aldıktan sonra onu analiz etmek için WinDbg veya Visual Studio kullanabilirsiniz. .NET uygulamaları için WinDbg’de SOS extension’ı yükleyerek managed heap’i incelemek mümkündür.
Otomatik Bellek Eşiği İzleme ve Alarm
Manuel takip yorucu ve güvenilmez. Şu basit script’i bir Scheduled Task’e bağlayarak düzenli çalıştırın; eşik aşıldığında size e-posta atsın ya da event log’a yazıp monitoring sisteminizin yakalamasını sağlasın:
# MemoryLeakMonitor.ps1
param(
[int]$EsikMB = 300, # Tek bir süreç bu kadar MB'ı geçerse uyar
[string]$SurecAdi = "w3wp", # İzlenecek süreç
[string]$LogDosyasi = "C:LogsMemoryAlarm.log"
)
$tarih = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$prosesler = Get-Process -Name $SurecAdi -ErrorAction SilentlyContinue
if (-not $prosesler) {
Write-Output "$tarih - $SurecAdi sureci bulunamadi." | Out-File $LogDosyasi -Append
exit
}
foreach ($p in $prosesler) {
$memMB = [math]::Round($p.WorkingSet64 / 1MB, 2)
if ($memMB -gt $EsikMB) {
$mesaj = "$tarih - UYARI: PID $($p.Id) ($SurecAdi) - Bellek: ${memMB} MB - Esik: ${EsikMB} MB"
Write-Output $mesaj | Out-File $LogDosyasi -Append
# Windows Event Log'a yaz
Write-EventLog -LogName Application -Source "MemoryMonitor" `
-EntryType Warning -EventId 9001 `
-Message $mesaj
}
}
Bu script’i her 5 dakikada bir çalıştırmak için Scheduled Task oluşturabilirsiniz:
# Scheduled Task oluştur
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument "-NonInteractive -File C:ScriptsMemoryLeakMonitor.ps1"
$trigger = New-ScheduledTaskTrigger -RepetitionInterval (New-TimeSpan -Minutes 5) `
-Once -At (Get-Date)
Register-ScheduledTask -TaskName "MemoryLeakMonitor" -Action $action `
-Trigger $trigger -RunLevel Highest -User "SYSTEM"
Bu yapıyla hem geçmişe yönelik log tutmuş olursunuz hem de monitoring aracınız event ID 9001’i yakalarsa size anlık bildirim gönderebilir.
Gerçek Dünya Senaryosu: Gecenin 2’sinde Sunucu Yanıt Vermez
Bir müşteride yaşadığım durumu anlatayım. E-ticaret platformu çalışıyor, her sabah saat 6-7 gibi sunucu ağırlaşıyor, ekip sunucuyu yeniden başlatıp işe devam ediyordu. Haftalarca böyle sürmüş.
İlk PerfMon loglarına baktığımda w3wp’nin private bytes değerinin gece 23:00’dan itibaren saatte yaklaşık 150 MB artarak sabah 6’ya kadar 1.2 GB’a ulaştığını gördüm. Gündüz trafiği altında bile bu kadar artmıyordu, ki bu çok ilginçti.
Procdump ile otomatik dump aldıktan sonra WinDbg + SOS ile incelediğimde yüzlerce açık kalmış SqlConnection nesnesi buldum. Gece çalışan bir batch job, veritabanı bağlantılarını using bloğu içinde açmıyordu; üstelik hata aldığında exception yakalanıyor ama connection dispose edilmiyordu. Bağlantı havuzu dolunca yeni connection üretiliyor, eski yarı-açık connection’lar GC tarafından toplanamıyordu.
Çözüm basitti: Tüm SqlConnection kullanımlarını using bloğuna almak. Ama tespit iki günü aldı. Doğru araçları önceden kurmak ve log toplamak o iki günü iki saate indirebilirdi.
Çözüm Yöntemleri: Tespiti Geçiyoruz, Ne Yapacağız?
Sızıntının kaynağına göre çözüm değişir:
Kullanıcı alanı uygulaması:
- Uygulama kodunu gözden geçirin, unmanaged kaynaklarda IDisposable pattern’i arayın
- Connection havuzu limitlerini
maxPoolSizeile kısıtlayın, en azından etki alanını daraltırsınız - IIS uygulama havuzunu bir bellek limiti ile configure edin:
Rapid Fail ProtectionveMemory Limitdeğerlerini kullanın - Kısa vadeli: w3wp’yi belirli aralıklarla
Recyclingile yeniden başlatacak şekilde ayarlayın (bu geçici çözümdür, kök neden bulunana kadar)
Kernel/driver sızıntısı:
- Poolmon ile tespit ettiğiniz tag’e ait driver’ı güncelleyin
- Şüpheli third-party driver’ı (anti-virüs agent, storage driver vb.) en son sürüme yükseltin
- Driver imzasını ve tarihini kontrol edin; eski ve imzasız driver’lar ciddi risk taşır
Windows bileşeni:
- Windows Update’i çalıştırın; bazı bellek sızıntıları bilinen bug’lar olup yamalarla kapatılmıştır
- Hotfix geçmişini kontrol edin:
Get-HotFix | Sort-Object InstalledOn -Descending | Select-Object -First 10
Hızlı servis için geçici müdahale:
# IIS uygulama havuzunu yeniden başlat (w3wp'yi kapatır, servis kesintisiz devam eder)
# Sadece belirtilen havuzu recycle eder
Restart-WebAppPool -Name "YourAppPoolName"
# Veya tüm havuzları sırayla
Get-WebConfiguration system.applicationHost/applicationPools/add |
ForEach-Object { Restart-WebAppPool $_.Name }
# IIS üzerinde bellek limiti tanımla (MB cinsinden)
# Bu değeri aştığında w3wp otomatik recycle olur
Set-WebConfiguration -Filter "system.applicationHost/applicationPools/add[@name='YourAppPoolName']/recycling/periodicRestart" `
-Value @{memory=2097152} # 2 GB (KB cinsinden)
Araç Seçimi: Neyi Ne Zaman Kullanmalısınız?
Her aracın bir doğal kullanım zamanı vardır:
- Task Manager: İlk bakış, hızlı sıralama. Yeterli değildir ama başlangıç noktasıdır.
- Resource Monitor: Task Manager’dan daha detaylı, özellikle physical memory breakdown için.
- Performance Monitor / logman: Uzun vadeli trend izleme, kanıt toplama, raporlama.
- ProcDump (Sysinternals): Koşul bazlı otomatik dump alma, üretimde en az müdahale ile çalışır.
- WinDbg + SOS: Managed heap analizi, derin inceleme, .NET sızıntılarında olmazmaz.
- poolmon.exe (WDK): Kernel pool sızıntılarında tag seviyesinde tespit.
- RAMMap (Sysinternals): Fiziksel belleğin kategori bazlı detaylı görünümü, Standby list vs. aktif kullanım ayrımı için ideal.
Sysinternals araçlarını her üretim sunucusuna kurup hazır bulundurmak, gece gelen bir alarm sırasında saatlerce uğraşmaktan sizi kurtarır. Kurulum bir dakika sürer ama o bir dakika gecenin 3’ünde paha biçilmezdir.
Sonuç
Windows’ta bellek sızıntısı tespit ve giderme, tek adımlı bir iş değildir. Önce “bu gerçekten sızıntı mı?” sorusunu cevaplandırın; çünkü Windows’un normal cache davranışını sızıntı zannetmek hem zaman çaldırır hem de gereksiz stres yaratır. Gerçek sızıntı tespit edildikten sonra trend izleme ile kaynağı daraltın, dump alarak kök nedeni bulun ve uygulamanın ya da driver’ın sahibiyle çözüme gidin. Kısa vadede IIS recycling ve bellek limitleri gibi koruyucu önlemler hayat kurtarır, ama bunlar birer bant-aid’dir; asıl iş kök nedeni ortadan kaldırmaktır.
Bu süreçte en büyük yardımcınız, sorunu görmeden önce kurulmuş bir izleme altyapısıdır. PerfMon log’ları, event log alarmları ve otomatik dump mekanizmalarını bugün kursanız, bir sonraki olayda “elimde hiç veri yok” diyerek baştan başlamak zorunda kalmazsınız.
