Bellek Sızıntısı Tespiti: Windows Performans Monitörü Kullanımı
Bir gün patronunuz sizi aceleyle çağırıyor: “Sunucu yavaşlıyor, müşteriler şikayet ediyor, ne yapacaksın?” Siz de sunucuya bağlanıyorsunuz, Task Manager’ı açıyorsunuz ve görüyorsunuz ki RAM kullanımı %95’te. Ama hangi uygulama bu belleği yiyor? İşte bu noktada Windows Performance Monitor devreye giriyor ve size gerçek anlamda hayat kurtarıcı oluyor.
Bellek sızıntısı (memory leak), bir uygulamanın kullandığı belleği işi bittikten sonra sisteme iade etmemesi durumudur. Küçük sızıntılar başlangıçta fark edilmez, ama saatler veya günler içinde sunucunuzu felç edebilir. Windows Server ortamında bu sorunu tespit etmek için en güçlü yerleşik araç Performance Monitor’dür (PerfMon). Bu yazıda gerçek dünya senaryoları üzerinden PerfMon’u kullanarak bellek sızıntısını nasıl tespit edeceğinizi adım adım anlatacağım.
Performance Monitor Nedir ve Neden Kullanmalısınız?
PerfMon, Windows Server’ın içine gömülü, ücretsiz ve son derece güçlü bir izleme aracıdır. Üçüncü parti araçlara para harcamadan önce PerfMon ile neler yapabileceğinize bir bakın:
- Gerçek zamanlı performans sayaçlarını izleyebilirsiniz
- Veri toplama setleri (Data Collector Sets) oluşturarak saatler, günler boyunca veri kaydedebilirsiniz
- Topladığınız verileri analiz edip raporlara dönüştürebilirsiniz
- Belirli eşik değerleri aşıldığında uyarı tetikleyebilirsiniz
- Birden fazla sunucuyu merkezi olarak izleyebilirsiniz
Bellek sızıntısı söz konusu olduğunda PerfMon’un değeri daha da artıyor, çünkü anlık snapshot yerine zaman içindeki değişimi görebiliyorsunuz. Bir uygulamanın belleği yavaş yavaş tükettiğini ancak trend analizi yaparak anlayabilirsiniz.
Temel Bellek Sayaçlarını Tanıyalım
PerfMon’da yüzlerce sayaç var ama bellek sızıntısı tespiti için odaklanmanız gereken spesifik sayaçlar var. Bunları iyi tanımadan doğru yorum yapmanız mümkün değil.
MemoryAvailable MBytes: Sistemde kullanılabilir fiziksel bellek miktarı. Bu değerin zamanla sürekli düşmesi ciddi bir uyarı işareti. Normal koşullarda dalgalanma olur ama tek yönlü düşüş tehlikeli.
MemoryCommitted Bytes: Sanal bellek için ayrılan toplam alan. Fiziksel RAM’in üzerine çıkabilir çünkü sayfalama dosyasını da kapsıyor. Bu değerin devamlı artması sızıntıya işaret eder.
MemoryPool Nonpaged Bytes: Kernel modu sürücülerinin kullandığı ve sayfalanamayan bellek havuzu. Sürücü kaynaklı sızıntılarda bu sayaç artıyor. Çok kritik bir sayaç.
MemoryPool Paged Bytes: Sayfalanabilir kernel belleği. Bu da artış trendi gösteriyorsa sorun var demektir.
ProcessPrivate Bytes: Her process için özel olarak ayrılan bellek. Belirli bir process izliyorsanız bu sayaç altın değerinde.
ProcessWorking Set: Process’in şu an fiziksel RAM’de tuttuğu bellek miktarı. Private Bytes ile birlikte değerlendirin.
ProcessVirtual Bytes: Process’in sanal adres alanı kullanımı. 32-bit uygulamalarda bu değer 2GB’ı geçemez, bu sınıra yaklaştığında uygulama çöküyor.
MemoryPages/sec: Bellek sayfalaması hızı. Yüksek değerler (sürekli 20-25 üzeri) thrashing yaşandığına işaret eder.
PerfMon ile İlk Kurulum: Veri Toplama Seti Oluşturma
Anlık izleme yapmak yerine veri toplama seti kurmanızı şiddetle tavsiye ederim. Sızıntılar çoğunlukla saatler içinde ortaya çıktığı için anlık bakış yeterli olmaz.
PerfMon’u açmak için:
# Run diyaloğundan veya PowerShell'den
perfmon.exe
# Ya da direkt veri toplama setleri konsolunu aç
perfmon /sys
Veri toplama seti oluşturmak için PowerShell ile de yapabilirsiniz, bu daha tekrar edilebilir ve otomatize edilebilir:
# Yeni bir Data Collector Set oluştur
$CollectorName = "MemoryLeakDetection"
$OutputPath = "C:PerfLogsMemoryLeak"
# Klasörü oluştur
New-Item -ItemType Directory -Path $OutputPath -Force
# logman ile veri toplama seti oluştur
logman create counter $CollectorName `
-cf "C:PerfLogsmemory_counters.txt" `
-f bincirc `
-max 512 `
-si 00:00:30 `
-o "$OutputPathMemoryLeak.blg"
Sayaç dosyasını oluşturun:
# memory_counters.txt içeriği
$counters = @"
MemoryAvailable MBytes
MemoryCommitted Bytes
MemoryPool Nonpaged Bytes
MemoryPool Paged Bytes
MemoryPages/sec
MemoryPage Faults/sec
Process(*)Private Bytes
Process(*)Working Set
Process(*)Virtual Bytes
Process(*)Page Faults/sec
"@
$counters | Out-File -FilePath "C:PerfLogsmemory_counters.txt" -Encoding ASCII
Veri toplamayı başlatın ve durdurun:
# Başlat
logman start $CollectorName
# Durdur (yeterli veri toplandıktan sonra)
logman stop $CollectorName
# Durumu kontrol et
logman query $CollectorName
Gerçek Dünya Senaryosu 1: IIS Uygulama Havuzu Sızıntısı
Müşterilerimden birinin web sunucusunda her gece yeniden başlatma yapıldığını öğrendim. “Neden?” diye sorduğumda “sunucu gece yarısı yavaşlıyor” dediler. Klasik bellek sızıntısı belirtisi.
Bu tip senaryolarda IIS uygulama havuzları ana şüpheli. Şöyle bir PowerShell scripti ile IIS worker process’lerini izleyebilirsiniz:
# IIS Worker Process'lerinin bellek kullanımını izle
function Get-IISWorkerMemory {
$w3wpProcesses = Get-Process -Name "w3wp" -ErrorAction SilentlyContinue
if ($w3wpProcesses) {
foreach ($proc in $w3wpProcesses) {
$appPool = (Get-WmiObject -Class Win32_Process -Filter "ProcessId=$($proc.Id)").CommandLine
[PSCustomObject]@{
ProcessId = $proc.Id
WorkingSetMB = [Math]::Round($proc.WorkingSet64 / 1MB, 2)
PrivateBytesMB = [Math]::Round($proc.PrivateMemorySize64 / 1MB, 2)
VirtualMB = [Math]::Round($proc.VirtualMemorySize64 / 1MB, 2)
CommandLine = $appPool
StartTime = $proc.StartTime
RunningHours = [Math]::Round((New-TimeSpan -Start $proc.StartTime -End (Get-Date)).TotalHours, 1)
}
}
} else {
Write-Host "Çalışan IIS Worker Process bulunamadı." -ForegroundColor Yellow
}
}
# Her 5 dakikada bir çalıştır ve logla
$logFile = "C:PerfLogsIIS_Memory_$(Get-Date -Format 'yyyyMMdd').csv"
while ($true) {
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$data = Get-IISWorkerMemory
if ($data) {
$data | Add-Member -NotePropertyName "Timestamp" -NotePropertyValue $timestamp
$data | Export-Csv -Path $logFile -Append -NoTypeInformation
Write-Host "[$timestamp] Veri kaydedildi." -ForegroundColor Green
}
Start-Sleep -Seconds 300
}
Bu scripti çalıştırıp birkaç saat bekledikten sonra CSV dosyasını açtığımızda, belirli bir uygulama havuzunun PrivateBytesMB değerinin saatler içinde sürekli arttığını gördüm. 300MB’dan başlayan değer 6 saat içinde 1.8GB’a ulaşmıştı. Sorun belliydi: kod tarafında bir bellek sızıntısı vardı.
Gerçek Dünya Senaryosu 2: Pool Nonpaged Bytes Sorunu
Bir başka vakada sunucu yeniden başlatılmadan önce “out of memory” hatası veriyordu ama Task Manager’da hiçbir uygulama anormal görünmüyordu. Bu durumda kernel tarafında sorun var demektir.
PerfMon’da MemoryPool Nonpaged Bytes sayacına bakın. Bu değer sürekli artıyorsa, büyük ihtimalle bir sürücü (driver) bellek sızıntısı yaşıyorsunuzdur.
Hangi sürücünün sorun çıkardığını tespit etmek için PoolMon kullanabilirsiniz:
# PoolMon'u Windows SDK ile kurun, sonra çalıştırın
# Alternatif olarak WinDbg kullanabilirsiniz
# Kernel debug için kısıtlı ama temel bilgi toplamak için:
# Hangi tag'ların en fazla bellek kullandığını görmek
# Performans sayaçlarından pool bilgisi almak
$counters = @(
"MemoryPool Nonpaged Bytes",
"MemoryPool Paged Bytes",
"MemoryPool Nonpaged Allocs",
"MemoryPool Paged Allocs"
)
$result = Get-Counter -Counter $counters -SampleInterval 60 -MaxSamples 60
$result.CounterSamples | ForEach-Object {
[PSCustomObject]@{
Counter = $_.Path
ValueMB = [Math]::Round($_.CookedValue / 1MB, 2)
Timestamp = $_.Timestamp
}
} | Format-Table -AutoSize
Pool Nonpaged Bytes değeri 100MB’ı geçtiyse ve artmaya devam ediyorsa, sürücü bazlı analiz yapmanız gerekiyor. Bu noktada WinDbg ile daha derine inmek gerekiyor ama bu ayrı bir yazı konusu.
PerfMon Uyarı Eşikleri Ayarlama
Sürekli ekran başında bekleyemezsiniz. PerfMon’da eşik değerleri aşıldığında otomatik uyarı almanızı sağlayan alerts özelliği var.
# PowerShell ile performance alert oluştur
# Kullanılabilir bellek 500MB altına düşünce uyar
logman create alert "LowMemoryAlert" `
-th "MemoryAvailable MBytes<500" `
-ex "C:Scriptsmemory_alert.bat" `
-si 00:01:00
# Alert başlat
logman start "LowMemoryAlert"
Uyarı geldiğinde çalışacak script:
# C:Scriptsmemory_alert.ps1
# Bu script uyarı geldiğinde çalışır ve anlık snapshot alır
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$reportPath = "C:PerfLogsAlertsAlert_$timestamp.txt"
New-Item -ItemType Directory -Path "C:PerfLogsAlerts" -Force | Out-Null
$report = @"
=== BELLEK UYARISI - $timestamp ===
Sistem Bellek Durumu:
"@
# Bellek bilgilerini al
$os = Get-WmiObject -Class Win32_OperatingSystem
$report += "`nToplam RAM: $([Math]::Round($os.TotalVisibleMemorySize / 1KB, 0)) MB"
$report += "`nKullanılabilir: $([Math]::Round($os.FreePhysicalMemory / 1KB, 0)) MB"
$report += "`nKullanım Oranı: $([Math]::Round((($os.TotalVisibleMemorySize - $os.FreePhysicalMemory) / $os.TotalVisibleMemorySize) * 100, 1))%"
# En fazla bellek kullanan top 10 process
$report += "`n`n=== TOP 10 BELLEK TÜKETİCİSİ ===`n"
$topProcesses = Get-Process | Sort-Object WorkingSet64 -Descending | Select-Object -First 10
foreach ($proc in $topProcesses) {
$report += "$($proc.Name) (PID: $($proc.Id)) - WS: $([Math]::Round($proc.WorkingSet64/1MB,1))MB - Private: $([Math]::Round($proc.PrivateMemorySize64/1MB,1))MB`n"
}
$report | Out-File -FilePath $reportPath -Encoding UTF8
# E-posta gönder (SMTP ayarlarınıza göre düzenleyin)
# Send-MailMessage -To "[email protected]" -Subject "UYARI: Sunucu Bellek Kritik Seviyede" -Body $report -SmtpServer "mail.sirket.com"
Write-Host "Rapor oluşturuldu: $reportPath"
Trend Analizi: Sızıntıyı Kanıtlamak
Veri topladınız, şimdi bu veriden anlamlı sonuç çıkarmanız lazım. Tek başına yüksek bellek kullanımı sızıntı değildir. Trend analizi yapmalısınız.
# PerfMon .blg dosyasından veri okuma ve analiz
# Önce relog ile CSV'e dönüştür
relog "C:PerfLogsMemoryLeakMemoryLeak.blg" -f CSV -o "C:PerfLogsMemoryLeakoutput.csv"
# Sonra PowerShell ile analiz et
$data = Import-Csv "C:PerfLogsMemoryLeakoutput.csv"
# Private Bytes sütununu bul ve trend hesapla
$processColumn = $data.PSObject.Properties.Name | Where-Object { $_ -like "*private bytes*" -and $_ -like "*w3wp*" }
if ($processColumn) {
$values = $data | Where-Object { $_.$processColumn -ne "" } | ForEach-Object {
[PSCustomObject]@{
Time = [DateTime]$_."(PDH-CSV 4.0)"
PrivateMB = [Math]::Round([double]$_.$processColumn / 1MB, 2)
}
}
# İlk ve son değerleri karşılaştır
$first = $values | Select-Object -First 1
$last = $values | Select-Object -Last 1
$diff = $last.PrivateMB - $first.PrivateMB
$hours = ($last.Time - $first.Time).TotalHours
$ratePerHour = [Math]::Round($diff / $hours, 2)
Write-Host "Analiz Sonucu:" -ForegroundColor Cyan
Write-Host "Başlangıç: $($first.PrivateMB) MB ($($first.Time))"
Write-Host "Bitiş: $($last.PrivateMB) MB ($($last.Time))"
Write-Host "Süre: $([Math]::Round($hours, 1)) saat"
Write-Host "Toplam artış: $diff MB"
Write-Host "Saatlik artış hızı: $ratePerHour MB/saat" -ForegroundColor $(if ($ratePerHour -gt 10) {"Red"} else {"Green"})
if ($ratePerHour -gt 10) {
Write-Host "UYARI: Bellek sızıntısı şüphesi! Saatte $ratePerHour MB artış." -ForegroundColor Red
}
}
Process’e Özgü Derin Analiz
Şüpheli process’i tespit ettikten sonra daha detaylı inceleme yapmanız gerekiyor. Handle sayısı ve thread sayısı da bellek sızıntısının yanında artıyorsa, sorun kesinleşiyor demektir:
# Belirli bir process'i derinlemesine izle
param(
[Parameter(Mandatory=$true)]
[string]$ProcessName,
[int]$IntervalSeconds = 60,
[int]$DurationMinutes = 60
)
$endTime = (Get-Date).AddMinutes($DurationMinutes)
$results = @()
Write-Host "Process izleniyor: $ProcessName" -ForegroundColor Cyan
Write-Host "Süre: $DurationMinutes dakika, Interval: $IntervalSeconds saniye" -ForegroundColor Cyan
while ((Get-Date) -lt $endTime) {
$processes = Get-Process -Name $ProcessName -ErrorAction SilentlyContinue
foreach ($proc in $processes) {
$sample = [PSCustomObject]@{
Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
PID = $proc.Id
WorkingSetMB = [Math]::Round($proc.WorkingSet64 / 1MB, 2)
PrivateBytesMB = [Math]::Round($proc.PrivateMemorySize64 / 1MB, 2)
VirtualMB = [Math]::Round($proc.VirtualMemorySize64 / 1MB, 2)
HandleCount = $proc.HandleCount
ThreadCount = $proc.Threads.Count
GDIObjects = $proc.GdiObjects
UserObjects = $proc.UserObjects
}
$results += $sample
Write-Host "$($sample.Timestamp) | PID: $($sample.PID) | Private: $($sample.PrivateBytesMB)MB | Handles: $($sample.HandleCount) | Threads: $($sample.ThreadCount)"
}
Start-Sleep -Seconds $IntervalSeconds
}
# Sonuçları dışa aktar
$outputFile = "C:PerfLogsProcessAnalysis_${ProcessName}_$(Get-Date -Format 'yyyyMMdd_HHmm').csv"
$results | Export-Csv -Path $outputFile -NoTypeInformation
Write-Host "`nSonuçlar kaydedildi: $outputFile" -ForegroundColor Green
# Özet istatistik
$privFirst = ($results | Select-Object -First 1).PrivateBytesMB
$privLast = ($results | Select-Object -Last 1).PrivateBytesMB
$handleFirst = ($results | Select-Object -First 1).HandleCount
$handleLast = ($results | Select-Object -Last 1).HandleCount
Write-Host "`n=== ÖZET ===" -ForegroundColor Yellow
Write-Host "Private Bytes: $privFirst MB --> $privLast MB (Fark: $([Math]::Round($privLast - $privFirst, 2)) MB)"
Write-Host "Handle Count: $handleFirst --> $handleLast (Fark: $($handleLast - $handleFirst))"
Geçici Çözümler ve Kalıcı Aksiyonlar
Sızıntıyı tespit ettikten sonra kısa vadeli ve uzun vadeli adımlar atmak gerekiyor.
Kısa vadeli (acil):
- IIS uygulama havuzu için bellek limiti ayarlayın. App Pool ayarlarında “Maximum Memory (in KB)” değerini doldurun, belirli bir eşiği geçince havuz otomatik yeniden başlar.
- Scheduled task ile problematik servisi belirli aralıklarla yeniden başlatın, köklü çözüme kadar zaman kazanırsınız.
- Windows Task Scheduler üzerinden gece maintenance penceresi oluşturun.
Uzun vadeli (kalıcı):
- Uygulamanın kaynak koduna erişiminiz varsa, geliştiricilere PerfMon verilerini gösterin. Somut veri olmadan “bellek sızıntısı var” demeniz yeterli ikna edici olmayabilir.
- .NET uygulamaları için CLR sayaçlarını da inceleyin: “.NET CLR Memory# Bytes in all Heaps” ve “.NET CLR MemoryLarge Object Heap size” sayaçları .NET tarafındaki bellek yönetimini gösterir.
- Production ortamında Application Pool recycling’i akıllıca yapılandırın, memory limit’i devreye alın.
PerfMon Verilerini Remote Sunuculardan Toplama
Birden fazla sunucunuz varsa merkezi izleme yapabilirsiniz:
# Uzak sunuculardan performans verisi topla
$servers = @("WebServer01", "WebServer02", "AppServer01")
$counters = @(
"MemoryAvailable MBytes",
"MemoryPool Nonpaged Bytes",
"MemoryCommitted Bytes"
)
$allResults = @()
foreach ($server in $servers) {
try {
Write-Host "Bağlanıyor: $server" -ForegroundColor Cyan
$remoteCounters = $counters | ForEach-Object { "\$server$_" }
$samples = Get-Counter -Counter $remoteCounters -SampleInterval 5 -MaxSamples 3 -ErrorAction Stop
foreach ($sample in $samples.CounterSamples) {
$allResults += [PSCustomObject]@{
Server = $server
Counter = $sample.Path -replace "\\$server", ""
ValueMB = [Math]::Round($sample.CookedValue / 1MB, 2)
Timestamp = $sample.Timestamp
}
}
Write-Host "$server: Veri alındı." -ForegroundColor Green
}
catch {
Write-Host "$server: Bağlantı hatası - $($_.Exception.Message)" -ForegroundColor Red
}
}
# Rapor oluştur
$allResults | Group-Object Server | ForEach-Object {
Write-Host "`n=== $($_.Name) ===" -ForegroundColor Yellow
$_.Group | Where-Object { $_.Counter -like "*Available*" } |
Select-Object -Last 1 |
ForEach-Object { Write-Host "Kullanılabilir RAM: $($_.ValueMB) MB" }
}
Sonuç
Bellek sızıntısı tespiti sabır isteyen bir iş. Tek bir anlık bakışla sorunu bulmanız çoğunlukla mümkün değil. PerfMon’un gücü tam da burada ortaya çıkıyor: zaman içindeki trendi yakalamanızı sağlıyor.
Bu yazıda öğrendiklerinizi özetleyecek olursam:
- Available MBytes, Pool Nonpaged Bytes ve Private Bytes sayaçları bellek sızıntısı tespitinin temel yapı taşları.
- Anlık izleme yerine Data Collector Set kurarak saatler boyu veri toplamalısınız. Sızıntılar sinsi sinsi büyür.
- IIS ortamında w3wp process’lerini Private Bytes ve Handle Count birlikte takip edin. Sadece bellek değil, handle sayısındaki artış da sızıntının belirtisi olabilir.
- Pool Nonpaged Bytes sürekli artıyorsa sorunu uygulama değil sürücü tarafında arayın.
- Topladığınız verileri trend analizi ile yorumlayın, mutlak değer değil değişim hızı önemli.
- Geliştiricilere somut veri götürün. “Sunucu yavaş” yerine “IIS uygulama havuzu saatte 45MB büyüyor, 6 saatte 1.8GB’a ulaşıyor” demek çok daha ikna edici.
PerfMon öğrenmesi biraz zaman alan ama bir kez özümsediğinizde vazgeçemeyeceğiniz bir araç. Üçüncü parti izleme çözümlerine para harcamadan önce bu yerleşik aracın ne kadar güçlü olduğuna şaşıracaksınız.
