Bellek Tükenmesi: Linux’ta OOM Durumunu Analiz Etme
Gece yarısı telefon çalıyor. Monitoring sisteminizden bir alarm geliyor: production sunucunuz yanıt vermiyor. SSH ile bağlanmaya çalışıyorsunuz, bağlantı çok yavaş kuruluyor ya da hiç kurulmuyor. Sonunda içeri girdiğinizde /var/log/syslog veya dmesg çıktısında o korkunç satırları görüyorsunuz: “Out of Memory: Kill process”. İşte bu an, her sysadmin’in en az bir kez yaşadığı OOM (Out of Memory) kabusu.
OOM durumu, Linux çekirdeğinin bellek yönetim sistemiyle doğrudan ilgilidir. Sistem kullanılabilir belleği tükettiğinde, çekirdek bir karar vermek zorunda kalır: ya çöker ya da bir şeyleri feda eder. Linux bu noktada OOM Killer devreye girer ve bir veya birden fazla süreci öldürerek sistemi kurtarmaya çalışır. Peki bu durumu nasıl analiz ederiz, nasıl önleriz ve tekrar yaşandığında ne yapmalıyız?
OOM’un Temel Mekanizması
Linux bellek yönetimi biraz karmaşık ama temeli basit: çekirdek, fiziksel RAM’den daha fazla bellek tahsis edilmesine izin verir. Bu “overcommit” mekanizması sayesinde uygulamalar ihtiyaç duydukları belleği hemen almak yerine, gerçekten kullandıklarında alırlar.
Sorun şu: Tüm uygulamalar aynı anda gerçekten belleği kullanmaya başlarsa, fiziksel RAM + swap toplamı yetmez hale gelir. Bu noktada çekirdek OOM durumunu ilan eder.
OOM Killer’ın hedef seçme mantığı oom_score değerine dayanır. Her sürecin bir skoru vardır ve bu skor ne kadar yüksekse, o süreç öldürülmeye o kadar adaydır.
# Tüm süreçlerin OOM skorlarını listele
for pid in /proc/[0-9]*; do
pid_num=$(basename $pid)
score=$(cat $pid/oom_score 2>/dev/null)
comm=$(cat $pid/comm 2>/dev/null)
echo "$score $pid_num $comm"
done | sort -rn | head -20
Bu komut size hangi süreçlerin OOM Killer’ın ilk hedefi olduğunu gösterir. Yüksek skor = yüksek risk.
OOM Loglarını Okumak
OOM durumu yaşandıktan sonra yapmanız gereken ilk şey logları incelemek. Çekirdek, OOM olayını birkaç farklı yere yazar.
# dmesg ile OOM loglarını bul
dmesg | grep -i "out of memory"
dmesg | grep -i "oom"
dmesg | grep -i "kill process"
# Zaman damgasıyla birlikte görmek için
dmesg -T | grep -i "oom|kill process|out of memory"
# Sistem loglarında OOM araması
grep -i "out of memory|oom_kill|memory killer" /var/log/syslog
grep -i "out of memory|oom_kill|memory killer" /var/log/messages
# journald kullanan sistemlerde
journalctl -k | grep -i "oom|out of memory|kill process"
journalctl --since "2 hours ago" | grep -i "oom"
Tipik bir OOM log çıktısı şöyle görünür:
[1234567.890] Out of memory: Kill process 4521 (java) score 847 or sacrifice child
[1234567.891] Killed process 4521 (java) total-vm:8192000kB, anon-rss:7654321kB, file-rss:12345kB
Bu çıktıda dikkat etmeniz gereken noktalar:
- score 847: OOM skoru, ne kadar yüksekse o kadar “tehlikeli”
- total-vm: Sürece tahsis edilen sanal bellek (gerçek kullanım değil)
- anon-rss: Anonim RSS, yani gerçekten fiziksel bellekte tutulan ve dosyaya dayanmayan veri
- file-rss: Dosyadan maplenen bellek miktarı
Anlık Bellek Durumunu Analiz Etme
OOM yaşandıktan hemen sonra sisteme bağlanabilirseniz, mevcut bellek durumunu hemen kayıt altına almalısınız.
# Genel bellek durumu
free -h
cat /proc/meminfo
# Bellek baskısını gösteren detaylı bilgi
vmstat -s
vmstat 1 10 # 1 saniyede bir, 10 kez örnekle
# Hangi süreçler ne kadar bellek kullanıyor
ps aux --sort=-%mem | head -20
# Smem ile daha doğru bellek ölçümü (varsa)
smem -r -k | head -20
/proc/meminfo çıktısını okumayı öğrenmek çok önemli. Özellikle şu alanlara dikkat edin:
- MemAvailable: Gerçekten kullanılabilir bellek (MemFree + cache/buffer’ın boşaltılabilir kısmı)
- SwapTotal / SwapFree: Swap alanı durumu
- Dirty: Diske yazılmayı bekleyen sayfalar
- Slab: Çekirdek veri yapıları için ayrılan bellek
# Slab bellek kullanımını detaylı incele
slabtop -s c # Boyuta göre sırala
cat /proc/slabinfo | sort -k 3 -rn | head -20
Gerçek Dünya Senaryosu: Java Uygulaması OOM Durumu
Bir e-ticaret şirketinde production Java uygulaması her gün belirli saatlerde OOM nedeniyle öldürülüyordu. Sabah 9-10 arası, yani kullanıcı trafiğinin arttığı saatlerde sistem belleği tükeniyordu.
İlk adım: Log analizi.
# Son 24 saatteki OOM eventlerini çıkar
journalctl -k --since "24 hours ago" | grep -i "oom|killed process" > /tmp/oom_events.txt
cat /tmp/oom_events.txt
# Kaç kez OOM yaşandı?
grep -c "Kill process|Killed process" /tmp/oom_events.txt
# Hangi süreçler öldürüldü?
grep "Killed process" /tmp/oom_events.txt | awk '{print $5}' | sort | uniq -c | sort -rn
İkinci adım: Java heap durumunu anlamak. OOM olayından önce toplanmış metrikler varsa:
# JVM heap kullanımını görüntüle (uygulama çalışıyorken)
jstat -gc $(pgrep java) 1000 10
# Java sürecinin bellek haritası
pmap -x $(pgrep java) | tail -5
# /proc altından bellek detayları
cat /proc/$(pgrep java)/status | grep -i "vmrss|vmswap|vmsize"
Bu analizde ortaya çıkan sorun: Java uygulaması -Xmx parametresi olmadan başlatılıyordu. JVM varsayılan olarak sistem belleğinin 1/4’ünü heap için alabilir ama bu değer kontrol altında değildi. Trafik artışında GC baskısı altında bellek kullanımı artıyor ve OOM Killer devreye giriyordu.
OOM Killer’ı Kontrol Etme
OOM Killer’ın davranışını belirli süreçler için ayarlayabilirsiniz. Bu çok kritik bir özellik; örneğin veritabanı sürecinizin asla öldürülmemesini isteyebilirsiniz.
# Bir süreci OOM Killer'dan koru (-17 = asla öldürme)
echo -1000 > /proc/$(pgrep mysqld)/oom_score_adj
# Bir süreci OOM Killer'ın ilk hedefi yap (1000 = en yüksek öncelik)
echo 1000 > /proc/$(pgrep some_app)/oom_score_adj
# Mevcut oom_score_adj değerini kontrol et
cat /proc/$(pgrep mysqld)/oom_score_adj
cat /proc/$(pgrep mysqld)/oom_score
oom_score_adj değerleri:
- -1000: Süreç asla öldürülmez
- 0: Varsayılan, normal hesaplama
- +1000: Süreç her zaman ilk hedef
Bu ayarı kalıcı yapmak için systemd service dosyanızı düzenlemeniz gerekir:
# Systemd service'e OOM ayarı ekle
systemctl edit mysql.service
# Açılan editöre şunu ekle:
[Service]
OOMScoreAdjust=-900
Bellek Sızıntısı Tespiti
OOM olaylarının önemli bir kısmı bellek sızıntısından kaynaklanır. Bir süreç zamanla belleği serbest bırakmadan kullanmaya devam ediyorsa, kaçınılmaz olarak OOM’a yol açar.
# Bir süreci zaman içinde izle
pid=$(pgrep myapp)
while true; do
rss=$(cat /proc/$pid/status | grep VmRSS | awk '{print $2}')
echo "$(date): RSS = $rss kB"
sleep 60
done
# Alternatif: ps ile periyodik izleme
watch -n 30 'ps aux --sort=-%mem | head -15'
# Valgrind ile bellek sızıntısı tespiti (geliştirme ortamında)
valgrind --leak-check=full --show-leak-kinds=all
--track-origins=yes
--verbose
--log-file=valgrind_output.txt
./myapplication
# Massif ile heap profiling
valgrind --tool=massif ./myapplication
ms_print massif.out.* | head -50
Kernel OOM Politikasını Ayarlama
Linux çekirdeğinin overcommit davranışını vm.overcommit_memory parametresiyle kontrol edebilirsiniz.
# Mevcut overcommit politikasını görüntüle
cat /proc/sys/vm/overcommit_memory
sysctl vm.overcommit_memory
# Overcommit politikası değerleri:
# 0: Heuristic (varsayılan), çekirdek kendi karar verir
# 1: Her zaman overcommit'e izin ver (malloc hiç başarısız olmaz)
# 2: Asla overcommit yapma (swap + RAM * overcommit_ratio kadar)
# Overcommit oranını ayarla (politika 2 ile kullanılır)
cat /proc/sys/vm/overcommit_ratio
sysctl vm.overcommit_ratio=80
# Kalıcı ayar için
echo "vm.overcommit_memory = 2" >> /etc/sysctl.conf
echo "vm.overcommit_ratio = 80" >> /etc/sysctl.conf
sysctl -p
Ayrıca vm.swappiness parametresi de çok önemlidir. Bu değer çekirdeğin ne kadar agresif şekilde swap kullanacağını belirler.
# Mevcut swappiness değeri
cat /proc/sys/vm/swappiness
# Database sunucuları için düşük swappiness önerilir
sysctl vm.swappiness=10
# Kalıcı yap
echo "vm.swappiness = 10" >> /etc/sysctl.conf
Pratik Monitoring Kurulumu
OOM durumlarını proaktif olarak izlemek için basit ama etkili bir monitoring scripti:
#!/bin/bash
# /usr/local/bin/memory_monitor.sh
THRESHOLD=90 # Yüzde cinsinden uyarı eşiği
LOG_FILE="/var/log/memory_monitor.log"
ALERT_EMAIL="[email protected]"
while true; do
# Kullanım yüzdesini hesapla
TOTAL=$(grep MemTotal /proc/meminfo | awk '{print $2}')
AVAILABLE=$(grep MemAvailable /proc/meminfo | awk '{print $2}')
USED=$((TOTAL - AVAILABLE))
PERCENT=$((USED * 100 / TOTAL))
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
if [ $PERCENT -gt $THRESHOLD ]; then
echo "$TIMESTAMP - UYARI: Bellek kullanimi %$PERCENT" >> $LOG_FILE
# En fazla bellek kullanan 5 süreci logla
echo "=== Top 5 Memory Consumers ===" >> $LOG_FILE
ps aux --sort=-%mem | head -6 >> $LOG_FILE
# /proc/meminfo snapshot
cat /proc/meminfo >> $LOG_FILE
# Email gönder (mail komutu yapılandırılmışsa)
echo "Bellek kullanimi kritik seviyede: %$PERCENT" |
mail -s "OOM Riski - $(hostname)" $ALERT_EMAIL
else
echo "$TIMESTAMP - OK: Bellek kullanimi %$PERCENT" >> $LOG_FILE
fi
sleep 60
done
Bu scripti systemd service olarak çalıştırabilir ya da cron ile tetikleyebilirsiniz.
OOM Sonrası Post-Mortem Analizi
OOM olayı yaşandıktan sonra düzgün bir post-mortem yapmanız gelecekteki olayları önlemek için kritiktir.
# OOM olayının tam zaman damgasını bul
journalctl -k | grep "Out of memory" | tail -20
# O anki sistem durumunu loglardan çıkarmaya çalış
# (Eğer daha önce sar/sysstat kuruluysa)
sar -r # Bellek kullanımı tarihçesi
sar -B # Sayfa hatası (page fault) istatistikleri
# Sysstat kurulu değilse, en azından şundan sonrasına kur
apt install sysstat # Debian/Ubuntu
yum install sysstat # RHEL/CentOS
# sysstat'ı aktifleştir
systemctl enable --now sysstat
# OOM dump analizi için crash/kdump kuruluysa
# Kernel crash dump'ı analiz et
crash /usr/lib/debug/boot/vmlinux-$(uname -r) /var/crash/vmcore
# Basit bir post-mortem raporu oluştur
cat > /tmp/oom_postmortem.sh << 'EOF'
#!/bin/bash
echo "=== OOM Post-Mortem Raporu ==="
echo "Tarih: $(date)"
echo "Hostname: $(hostname)"
echo ""
echo "=== OOM Olaylari (son 7 gun) ==="
journalctl -k --since "7 days ago" | grep "Kill process|Killed process"
echo ""
echo "=== Mevcut Bellek Durumu ==="
free -h
echo ""
echo "=== Swap Kullanimi ==="
swapon --show
echo ""
echo "=== En Fazla Bellek Kullanan Surecler ==="
ps aux --sort=-%mem | head -15
echo ""
echo "=== Son OOM Mesajlari ==="
dmesg -T | grep -i "oom" | tail -20
EOF
chmod +x /tmp/oom_postmortem.sh
bash /tmp/oom_postmortem.sh > /tmp/oom_report_$(date +%Y%m%d).txt
Gerçek Dünya Senaryosu: Redis + PHP-FPM Birlikteliği
Bir içerik platformunda Redis ve PHP-FPM aynı sunucuda çalışıyordu. Gece saatlerinde background job’lar devreye girdiğinde sistem OOM’a düşüyordu.
Analiz süreci:
# Hangi süreçlerin ne kadar bellek kullandığını anla
ps -eo pid,ppid,cmd,%mem,%cpu --sort=-%mem | head -30
# Redis bellek kullanımı
redis-cli info memory | grep -E "used_memory_human|maxmemory_human|mem_fragmentation_ratio"
# PHP-FPM worker sayısı ve bellek kullanımı
ps aux | grep php-fpm | grep -v grep | wc -l
ps aux | grep php-fpm | grep -v grep | awk '{sum += $6} END {print sum/1024 " MB"}'
Çözüm: Redis’e maxmemory limiti koymak ve maxmemory-policy allkeys-lru ayarlamak, PHP-FPM’de pm.max_children değerini sisteme göre hesaplamak.
# Redis maxmemory ayarı
redis-cli config set maxmemory 2gb
redis-cli config set maxmemory-policy allkeys-lru
# PHP-FPM için basit bellek hesabı
# (Toplam RAM - OS ve diğer servisler) / PHP worker başına ortalama bellek
# Örnek: (8GB - 3GB) / 50MB = 100 worker max
Swap Yönetimi
OOM durumlarında swap da kritik bir rol oynar. Swap’ın olmadığı sistemlerde OOM daha erken tetiklenir.
# Swap durumunu kontrol et
free -h
swapon --show
cat /proc/swaps
# Swap dosyası oluştur (acil durum için)
dd if=/dev/zero of=/swapfile bs=1G count=4
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
# Kalıcı yapmak için /etc/fstab'a ekle
echo "/swapfile none swap sw 0 0" >> /etc/fstab
# Hangi süreçler swap kullanıyor?
for pid in /proc/[0-9]*/status; do
swap=$(grep VmSwap $pid 2>/dev/null | awk '{print $2}')
comm=$(grep Name $pid 2>/dev/null | awk '{print $2}')
if [ "$swap" -gt "0" ] 2>/dev/null; then
echo "$swap kB - $comm (PID: $(basename $(dirname $pid)))"
fi
done | sort -rn | head -20
Önleyici Tedbirler
OOM durumlarını önlemek için uygulamanız gereken bazı temel pratikler:
- Cgroup ile bellek sınırlama: Systemd service’lere
MemoryLimitdirektifi ekleyin. ÖrneğinMemoryLimit=2Gile bir servisin en fazla 2GB kullanmasına izin verebilirsiniz.
- Uygulama seviyesinde limitler: Java’da
-Xmx, Node.js’de--max-old-space-sizegibi parametrelerle uygulamanın bellek tavanını belirleyin.
- Düzenli bellek profiling: Özellikle yeni bir deployment sonrasında bellek kullanımını günler boyunca izleyin.
- Alert eşiklerini doğru kur: %90 eşiği çok geç uyarı verebilir. Kritik sistemler için %75-80 daha makul bir eşiktir.
- OOM Kill loglarını merkezi sisteme gönder: ELK Stack veya benzeri bir log yönetim sistemine OOM eventlerini yönlendirin.
- Kapasiteyi düzenli değerlendirin: Aylık bazda bellek trendlerini inceleyin ve sunucu RAM’ini artırmak ya da servisleri dağıtmak gerekip gerekmediğine karar verin.
# Cgroup ile bellek limiti (systemd)
# /etc/systemd/system/myapp.service dosyasına ekle:
# [Service]
# MemoryLimit=2G
# MemorySwapMax=0 # Swap kullanımını engelle
# Konteyner ortamı için (Docker)
docker run --memory="2g" --memory-swap="2g" myimage
# Mevcut cgroup bellek limitlerini kontrol et
cat /sys/fs/cgroup/memory/system.slice/myapp.service/memory.limit_in_bytes
Sonuç
OOM durumu yaşandığında paniklemek yerine sistematik bir yaklaşım izlemek hayat kurtarır. Özetle yapmanız gerekenler şunlar:
İlk müdahalede dmesg -T | grep oom ile ne zaman ve hangi sürecin öldürüldüğünü öğrenin. Ardından ps aux --sort=-%mem ve free -h ile mevcut durumu kayıt altına alın. Kısa vadede kritik servislerinizi oom_score_adj=-1000 ile koruma altına alın.
Orta vadede sysstat kurarak tarihsel veri toplamaya başlayın, monitoring alert eşiklerinizi ayarlayın ve uygulama bellek limitlerini gözden geçirin. Uzun vadede cgroup limitleri, düzenli profiling ve kapasite planlamasıyla OOM olaylarının önüne geçebilirsiniz.
OOM Killer bir düşman değil, sisteminizi korumaya çalışan bir mekanizmadır. Onu anladığınızda, onunla çalışmak çok daha kolay hale gelir. Asıl hedef, OOM Killer’ın hiç devreye girmesine gerek kalmayan bir sistem tasarlamaktır.
