Bugün size nasıl yardımcı olabiliriz?

Bilgi bankamızda arama yapmak için sorunuzu yazın...

Linux Sunucuda OOM Killed Hatası: Bellek Tüketimi Nasıl İncelenir?

Yazdır

Netx Teknik Ekibi  ·  Yayın: 29.10.2026

"Out of memory: Killed process" satırı, Linux çekirdeğinin bellek tükendiğinde sistemi ayakta tutmak için bir işlemi seçip sonlandırdığını gösterir. Öldürülen işlem çoğu zaman suçlu değil, o anda en çok bellek tutan işlemdir: MySQL kapanır ama belleği yiyen, sayısı kontrolsüz artan PHP işçileridir. Teşhis sırası şöyledir: dmesg -T ya da journalctl -k ile kaydı bulun, satırdaki constraint alanından sorunun bütün sistemde mi yoksa bir cgroup sınırında (systemd MemoryMax, Docker bellek sınırı) mı yaşandığını ayırın, aynı kayıttaki işlem tablosundan tüketiciyi çıkarın ve PHP-FPM, veritabanı ve önbellek ayarlarını bellek bütçesine göre düzeltin.

Kanıt hangi günlükte durur?

Tipik başlangıç şöyle: gece veritabanı durmuş, sabah site "veritabanı bağlantı hatası" veriyor, servis yeniden başlatılınca her şey düzeliyor. Kimse bir şey yapmamış gibi görünür. Oysa çekirdek ne yaptığını kaydetmiştir.

dmesg -T | grep -i -E 'out of memory|killed process|oom-kill'

-T zaman damgalarını okunur saate çevirir. man sayfası, sistem uyku moduna alınıp uyandırıldıysa bu saatlerin kayabileceğini not ediyor. Sunucularda nadir bir durumdur ama saat tutmuyorsa sebebi budur. Çekirdek arabelleği sınırlıdır, yeniden başlatmada da silinir. Bu yüzden önceki açılışlar için systemd günlüğüne bakın:

journalctl --list-boots
journalctl -k -b -1 | grep -i -E 'out of memory|killed process'

-k yalnız çekirdek iletilerini gösterir ve aksi belirtilmedikçe içinde bulunulan açılışla sınırlıdır. -b -1 bir önceki açılışı seçer. Günlük kalıcı tutulmuyorsa eski açılışlar listede görünmez. rsyslog kuruluysa aynı satırlar RHEL ailesinde (AlmaLinux, Rocky Linux) /var/log/messages, Debian ve Ubuntu'da /var/log/kern.log ya da /var/log/syslog dosyasına da yazılır.

Servis tarafında da iz kalır. systemd, işlemi OOM ile öldürülen bir birim durduğunda Failed with result 'oom-kill' yazar. PHP-FPM ise kendi günlüğüne child 1234 exited on signal 9 (SIGKILL) biçiminde bir satır bırakır:

journalctl -u mariadb --since "2026-10-28 00:00" | grep -i oom

Birim adı kuruluma göre değişir: mariadb, mysqld, mysql ya da php8.3-fpm gibi.

Kayıt satırını okumak

Bir OOM olayı tek satır değil, bir blok bırakır. Varsayımsal senaryo: gece 03:12'de MariaDB öldürülmüş. Bloğun kısaltılmış hâli şöyle görünür:

php-fpm invoked oom-killer: gfp_mask=0x140cca(GFP_HIGHUSER_MOVABLE|__GFP_COMP), order=0, oom_score_adj=0
...
Tasks state (memory values in pages):
[  pid  ]   uid  tgid total_vm      rss pgtables_bytes swapents oom_score_adj name
[   1187]    27  1187   873210   402115  4128768        0             0 mariadbd
[   2290]  1001  2290    98410    31240   557056        0             0 php-fpm
[   2291]  1001  2291    98410    30100   557056        0             0 php-fpm
...
oom-kill:constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=/,mems_allowed=0,global_oom,task_memcg=/system.slice/mariadb.service,task=mariadbd,pid=1187,uid=27
Out of memory: Killed process 1187 (mariadbd) total-vm:3492840kB, anon-rss:1608460kB, file-rss:0kB, shmem-rss:0kB, UID:27 pgtables:4032kB oom_score_adj:0

İlk satırdaki php-fpm invoked oom-killer, bellek isteyip alamayan işlemi gösterir. Tetikleyendir, mutlaka suçlu değildir. order=0 tek sayfalık sıradan bir istek demektir. Sıfırdan büyük bir değer, çekirdeğin bitişik bellek bloğu aradığını anlatır.

oom-kill:constraint satırı en önemli ayrımı yapar. CONSTRAINT_NONE ve global_oom bütün sistemin belleğinin bittiğini söyler. CONSTRAINT_MEMCG ve oom_memcg=... ise bir cgroup sınırına çarpıldığını gösterir. task_memcg, öldürülen işlemin hangi servise ya da konteynere ait olduğunu verir.

Son satırda öldürülen işlemin bellek dökümü var. total-vm sanal adres alanıdır ve gerçek tüketimi göstermez, o yüzden ona takılmayın. Asıl rakam anon-rss, yani işleme ait anonim (dosyaya bağlı olmayan) yerleşik bellektir. file-rss bellekteki dosya eşlemelerini, shmem-rss paylaşılan belleği sayar. Cgroup sınırına çarpan olaylarda satır Memory cgroup out of memory: Killed process diye başlar.

Tablodaki rss ve diğer bellek sütunları kilobayt değil sayfa cinsindendir. Yaygın x86_64 sunucularda bir sayfa 4 KiB'tır, yani 402115 sayfa yaklaşık 1,5 GiB eder. Sütun sırası çekirdek sürümüne göre değişir. 6.x çekirdeklerde rss'in yanında rss_anon, rss_file ve rss_shmem de yer alır.

Kurban ile suçlu aynı işlem değildir

Çekirdek kurbanı bir puanla seçer. Puan büyük ölçüde işlemin bellekteki ayak iziyle belirlenir ve oom_score_adj değeri (-1000 ile 1000 arası) bu puana eklenir. Bu yüzden tek parça hâlinde en çok bellek tutan işlem, genellikle veritabanı ya da bir Java uygulaması, ilk adaydır. Belleği tüketen ise küçük ama kalabalık bir grup olabilir: 60 PHP işçisinin her biri 90 MB tutuyorsa toplamda veritabanından fazlasını yer, ama tek tek hiçbiri en büyük değildir.

Olay anındaki tabloyu programa göre toplamak gerçeği çabuk gösterir:

dmesg | awk '
/\[  pid  \]/ { for (i = 1; i <= NF; i++) if ($i == "rss") c = i - NF; f = 1; next }
/oom-kill:/   { f = 0 }
f             { r[$NF] += $(NF + c) }
END { for (k in r) printf "%-16s %8.0f MB\n", k, r[k] * 4 / 1024 }
' | sort -k2 -rn | head

Betik, tablo başlığından rss sütununun yerini kendisi bulur. Bu sayede 5.x ve 6.x çekirdeklerin farklı sütun düzeninde de çalışır. Sayfa boyutunu 4 KiB varsayar. Arabellekte birden fazla OOM olayı varsa toplamlar birbirine karışır, o durumda tek bir olayın satırlarını ayrı bir dosyaya alıp betiği onun üzerinde çalıştırın. Çıktıda php-fpm toplamı veritabanını geçiyorsa düzeltilecek yer bellidir.

Şu anki adayları görmek isterseniz her işlemin güncel puanı /proc/<pid>/oom_score dosyasında durur:

for p in /proc/[0-9]*; do
  printf "%s %s %s\n" "$(cat $p/oom_score 2>/dev/null)" "${p#/proc/}" "$(cat $p/comm 2>/dev/null)"
done | sort -rn | head

Belleği şu an kim tüketiyor?

Olay geçmişte kaldıysa bugünkü tabloya bakarak eğilimi görürsünüz. En büyük tekil işlemler için:

ps -eo pid,user,rss,comm --sort=-rss | head -15

Aynı programın bütün kopyalarını toplamak için:

ps -eo rss=,comm= \
  | awk '{r[$2] += $1} END {for (k in r) printf "%-16s %8.0f MB\n", k, r[k] / 1024}' \
  | sort -k2 -rn | head

rss kilobayt cinsindendir ve paylaşılan sayfaları her kopyaya ayrı ayrı yazar. PHP işçilerinin toplamı bu yüzden biraz şişkin görünür. smem kuruluysa paylaşılan belleği bölüştüren PSS değeri daha dürüst bir toplam verir.

Servis bazında görmek için systemd-cgtop -m birimleri bellek kullanımına göre sıralar. Bu sayıya servisin kullandığı dosya önbelleği de dahildir. Bol dosya okuyan bir servis olduğundan büyük görünebilir, o yüzden ps çıktısıyla birlikte okuyun. Bellek bütçesinin genel mantığını, hangi bileşenin kabaca ne kadar yer kapladığını sunucuda ne kadar RAM gerektiğini hesaplama rehberimizde anlattık, burada tekrar etmeyelim.

Bütün sistem mi, tek servis mi?

Sık yapılan hata, cgroup sınırından gelen bir OOM'u "sunucunun belleği yetmiyor" diye okumaktır. free -h bolca boş bellek gösterirken bir servisin OOM ile öldürülmesi tam olarak bu durumdur.

cgroup v2 kullanan güncel dağıtımlarda her servis kendi kontrol grubunda çalışır. Çekirdek belgesine göre bir grubun kullanımı memory.max sınırına ulaşır ve geri kazanılamazsa OOM killer o grubun içinde çalışır. memory.high ise farklıdır: aşıldığında işlemler yavaşlatılır ve bellek agresif biçimde geri alınır, ama bu sınır OOM killer'ı hiç çağırmaz. systemd'deki MemoryMax= ve MemoryHigh= ayarları bu iki dosyaya karşılık gelir. Bir servisin sınırına ve sayaçlarına bakmak için:

systemctl show mariadb -p MemoryMax -p MemoryHigh -p MemoryCurrent -p OOMPolicy -p Restart
cat /sys/fs/cgroup/system.slice/mariadb.service/memory.events

memory.events içindeki oom_kill, o gruptan OOM ile öldürülen işlem sayısıdır. max ise kullanımın üst sınırı aşmak üzere olduğu anları sayar. MemoryMax değeri infinity değilse ve oom_kill sıfırdan büyükse sorun sunucunun belleğinde değil, servise konmuş sınırdadır.

OOMPolicy değeri servisin olaydan sonra ne yapacağını belirler. continue olayı kaydedip servisi çalışır bırakır, stop servisi düzgünce durdurur, kill ise grubun kalan işlemlerini de öldürtür. stop ve kill durumlarında servis oom-kill hatasıyla başarısız sayılır, ardından Restart= ayarı devreye girebilir. systemd'nin ayar dosyasında varsayılan DefaultOOMPolicy=stop olarak gelir. Restart= tanımlı değilse veritabanı siz elle başlatana kadar kapalı kalır. Gece yaşanan bir olayın sabaha kadar sürmesinin olası nedenlerinden biri budur.

Docker'da -m (--memory) ile konan sınır aynı mekanizmayla çalışır. Konteynerin OOM ile durup durmadığını docker inspect --format '{{.State.OOMKilled}}' konteyner_adi gösterir. Bazı dağıtımlarda çekirdekten önce davranan kullanıcı alanı aracı systemd-oomd da etkin olabilir. systemctl is-active systemd-oomd çıktısı active ise onun kararlarını journalctl -u systemd-oomd ile ayrıca kontrol edin.

Tipik nedenler ve düzeltmeler

PHP-FPM işçi sayısı belleğe sığmıyor

Web sunucularında ilk bakılacak yer burasıdır. pm.max_children, havuzun aynı anda açabileceği en fazla işçi sayısıdır ve bütçeye uymalıdır:

pm.max_children ≤ PHP'ye bırakılan bellek ÷ işçi başına ortalama bellek

PHP'ye 2 GB bırakabiliyorsanız ve işçiler ortalama 100 MB tutuyorsa üst sınır 20'dir. 50 yazarsanız trafik sakinken hiçbir şey olmaz, ilk bot dalgasında bellek biter. Sunucuda birden çok site varsa hesabın havuzlar toplanarak yapılması gerekir. Kâğıt üzerinde makul görünen havuzların toplamının çok siteli bir VDS'te bellek bütçesini nasıl aştığı ayrı bir hesap konusudur. Uzun ömürlü işçilerde bellek yavaş yavaş şişiyorsa pm.max_requests değeri, işçinin belirli sayıda istekten sonra yeniden açılmasını sağlar. PHP belgesi de bu ayarı üçüncü taraf kütüphanelerdeki sızıntılara karşı geçici bir çare olarak anıyor.

Veritabanı tamponu sunucunun tamamını varsayıyor

MySQL belgesi, yalnız veritabanı çalışan sunucularda belleğin %80'ine kadarının sıklıkla buffer pool'a ayrıldığını yazar. Aynı sunucuda web sunucusu ve PHP de çalışıyorsa bu oran felakettir. innodb_buffer_pool_size değerini, sıcak veriyi taşıyacak ama PHP'ye yer bırakacak düzeyde tutun. Bağlantı başına ayrılan tamponlar max_connections ile çarpılarak bunun üzerine biner. MySQL 8.4'te buffer pool boyutu SET GLOBAL ile servis çalışırken değiştirilebilir. Kalıcı olması için değeri ayar dosyasına da yazın.

Redis'e sınır konmamış

Redis belgesine göre 64 bit sistemlerde maxmemory varsayılan olarak 0'dır, yani veri kümesine sınır yoktur. Nesne önbelleği olarak kullanılan bir Redis, sınır konmadıkça sitenin büyüdüğü ölçüde büyür. maxmemory 512mb gibi açık bir sınır ve önbellek kullanımına uygun bir maxmemory-policy (çoğu önbellek senaryosunda allkeys-lru) tanımlayın. Varsayılan noeviction politikasında sınır dolunca Redis yeni veri yazan komutlara hata döndürür, bunu da uygulamanın nasıl karşıladığını bilmeniz gerekir.

Java uygulamasının yığın sınırı yok

JVM'e -Xmx ile yığın (heap) üst sınırı verilmezse varsayılanı makinedeki belleğe göre seçer. Yığın dışı bellek (metaspace, iş parçacığı yığınları, doğrudan tamponlar) bunun da üzerine eklenir. Aynı sunucuda başka servisler varsa sınırı açıkça yazın ve toplamı bütçeye katın.

Sızıntı ya da gece işleri

Olaylar hep aynı saatte oluyorsa zamanlanmış işlere bakın: gece yedeği, sıkıştırma, toplu içe aktarma ve rapor üretimi aynı dakikaya denk gelmiş olabilir. Rastgele saatlerde ama birkaç günde bir oluyorsa bellek sızıntısından şüphelenin. Uzun süredir çalışan işlemin belleğini aralıklarla kaydedin:

pidstat -r -p 1187 600

Komut 1187 numaralı işlemin RSS değerini on dakikada bir yazar. Gün boyunca yük azalırken bile sürekli artıyorsa sızıntı vardır. Kalıcı çözüm yazılım güncellemesi ya da hatanın düzeltilmesidir, planlı yeniden başlatma ise yalnız zaman kazandırır.

Takas tampondur, çözüm değil

Takas alanı (swap), bellek dolduğunda çekirdeğe nefes alacak yer verir. OOM'u geciktirir, bazen de kısa bir tepeyi atlatmanızı sağlar. Sunucu sürekli takasa yazıp okuyorsa ise her şey yavaşlar ve sonunda yine OOM gelir. Hiç takası olmayan küçük bir sunucuya küçük bir takas dosyası eklemek mantıklıdır:

dd if=/dev/zero of=/swapfile bs=1M count=2048
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap defaults 0 0' >> /etc/fstab

Komutlar 2 GB'lık bir takas dosyası oluşturur, yalnız root'un okuyabileceği hâle getirir, takas olarak biçimlendirir, devreye alır ve açılışta otomatik bağlanması için /etc/fstab'a ekler. /etc/fstab'ı düzenlemeden önce bir kopyasını alın. Bu dosyadaki hatalı bir satır sunucunun açılmasını engelleyebilir. swapon --show ile takasın etkin olduğunu doğrulayın. Çekirdeğin takasa ne kadar istekli davranacağını vm.swappiness (0–200, varsayılan 60) belirler. Bellek yetmezliğinin kalıcı çözümü yine işçi sayısını ve tamponları bütçeye uydurmak ya da belleği artırmaktır.

Kritik servisi korumanın bedeli

"Veritabanını OOM'dan muaf tutalım" fikri cazip gelir. systemd'de bir ek ayar dosyasıyla yapılır:

systemctl edit mariadb
# açılan düzenleyiciye:
[Service]
OOMScoreAdjust=-500

-500 veritabanını aday listesinde geriye iter ama korumayı mutlak yapmaz. -1000 ise o servis için OOM öldürmesini tamamen kapatır. Bedeli şudur: belleği tüketen veritabanının kendisi olsa bile çekirdek başka bir şeyi öldürmek zorunda kalır. Sıradaki aday SSH oturumunuz, web sunucusu ya da izleme ajanı olabilir. Öldürülebilecek işlem kalmazsa çekirdek System is deadlocked on memory iletisiyle paniğe geçer.

Bizce daha sağlıklı yol, korumak yerine gürültücü tarafı sınırlamaktır. PHP-FPM servisine MemoryHigh ve MemoryMax koyarsanız PHP önce yavaşlatılır, sınır aşılırsa OOM yalnız PHP'nin grubunda çalışır ve veritabanı etkilenmez. systemd belgesi de MemoryHigh'ı ana denetim aracı, MemoryMax'ı son savunma hattı olarak tarif ediyor:

systemctl edit php8.3-fpm
# açılan düzenleyiciye:
[Service]
MemoryHigh=2G
MemoryMax=2500M

Servis adı dağıtıma göre değişir: RHEL ailesinde php-fpm, Debian ve Ubuntu'da php8.3-fpm gibi sürüm numaralı bir addır. Değişikliğin ardından servisi yeniden başlatın ve systemctl show ile değerlerin uygulandığını kontrol edin.

Düzeltmeyi doğrulamak

Ayar değişikliğinin işe yaradığını tek bir sakin günde anlayamazsınız. Olayın yaşandığı türden bir yoğunluk, yani aynı gece işi ya da benzer bir trafik dalgası geçtikten sonra kontrol edin:

  • journalctl -k -f açıkken yoğun saat ya da gece işi geçiyor ve yeni Killed process satırı düşmüyor.
  • İlgili servisin memory.events dosyasındaki oom_kill sayacı artmıyor.
  • free -h çıktısındaki available değeri tepe anında da belirgin bir pay bırakıyor.
  • /proc/pressure/memory dosyasındaki full avg60 değeri sıfıra yakın. Çekirdek belgesi, bu değerde uzun süre kalan bir iş yükünü "thrashing" olarak tanımlıyor.
  • PHP-FPM günlüğünde exited on signal 9 satırı yok.

OOM kayıtları yok ama sayfalar ağırlaştıysa, bu kez yavaşlığın steal, iowait ya da load average'dan hangisinde göründüğünü ayırmak gerekir. Yanıt süresi uzayıp hata çıkmıyorsa darboğaz büyük olasılıkla işlemcidedir. İşlemci tahsisinin nasıl okunacağını vCPU, thread ve ayrılmış işlemci farkını anlattığımız yazıda bulabilirsiniz. Paylaşımlı hostingte ise bellek sınırına takılan bir site OOM kaydı yerine farklı bir hata gösterir. CloudLinux kullanan sunucularda bu çoğu zaman 508 Resource Limit Reached kaynak sınırı hatası olarak karşınıza çıkar.

Değişiklikleri geri almak

Her ayar dosyasını düzenlemeden önce yanına tarihli bir kopya bırakın:

cp -a /etc/php/8.3/fpm/pool.d/www.conf /etc/php/8.3/fpm/pool.d/www.conf.yedek-$(date +%F)

Yol dağıtıma ve panele göre değişir. RHEL ailesinde havuz dosyaları genellikle /etc/php-fpm.d/ altındadır. systemd ek ayarlarını geri almak için systemctl revert mariadb komutu, birime ait bütün ek ayar dosyalarını kaldırıp üreticinin sürümüne döndürür. Ardından servisi yeniden başlatın. SET GLOBAL ile yapılan veritabanı değişiklikleri servis yeniden başladığında kendiliğinden kaybolur. Takas dosyasını kaldırmak için önce swapoff /swapfile çalıştırın, sonra /etc/fstab'daki satırı silin ve dosyayı kaldırın. Neyi ne zaman değiştirdiğinizi kısa bir notla tutmak, bir sonraki olayda hangi ayarın etkili olduğunu ayırmanızı kolaylaştırır.

Ne zaman destek ekibine yazmalı?

OOM yüzünden SSH oturumu da öldürüldüyse ve sunucuya bağlanamıyorsanız, sağlayıcınız sanal konsol sunuyorsa ilk kurtarma yolu odur. Konsol yoksa destek ekibinden yeniden başlatma isteyin. Netx müşteri panelinden destek talebi açarken olay saatini (saat dilimini belirterek), dmesg ya da journalctl -k çıktısından invoked oom-killer satırından Killed process satırına kadar olan bloğu, free -h çıktısını ve son değiştirdiğiniz ayarları ekleyin.

Bütçeyi doğru kurduğunuz hâlde ölçüm gerçekten daha fazla bellek gerektiğini gösteriyorsa yükseltme artık tahmine dayanmaz. Netx VDS paketlerinde paketteki RAM ve vCore size ayrılmış kaynak olarak tanımlanır. Bellek ayarlarını her kampanyadan, yeni eklentiden ya da büyümeden sonra yeniden gözden geçirmek düzenli bir iştir. Bunu üstlenecek biri yoksa sunucu yönetimi kapsamında devredebilirsiniz, hangi işlerin dahil olduğu teklifte yazılı olarak belirlenir.

Sık sorulan sorular

OOM killer'ı tamamen kapatabilir miyim?

Önerilmez. Çekirdeğin vm.panic_on_oom ayarı 1 yapılırsa bellek bittiğinde işlem öldürülmez, çekirdek paniğe geçer. Sunucu, kernel.panic ayarına göre ya durur ya da yeniden başlar. Tek bir servisi kaybetmek yerine bütün sunucuyu kaybedersiniz. Çekirdek belgesi bu seçeneği küme (cluster) ortamlarında devralma (failover) için tanımlıyor, tek sunucu için değil.

free -h boş bellek gösteriyordu, yine de OOM oldu. Nasıl?

Üç olasılık var. Birincisi cgroup sınırıdır: kayıtta CONSTRAINT_MEMCG görüyorsanız servis kendi sınırına çarpmıştır. İkincisi kısa bir tepedir: bellek birkaç saniye içinde dolup boşalmış, siz baktığınızda iş bitmiştir. Üçüncüsü order değeri sıfırdan büyük bir istektir: çekirdek bitişik bir bellek bloğu bulamamış olabilir.

Kayıtta OOM yok ama MySQL kendiliğinden yeniden başlıyor

Önce journalctl -u mariadb (ya da mysqld) ve veritabanının kendi hata günlüğüne bakın. Çökme başka bir sinyalden ya da bir yazılım hatasından kaynaklanıyor olabilir. Konteyner içinde çalışıyorsa OOM kaydı konteynerin değil, ana makinenin çekirdek günlüğünde durur.

Bu cevap yeterince yardımcı oldu mu?

Değerlendirme: 0

<< Geri
İnternet bağlantınız kesildi