OOM killer 発動時の対処 - メモリ不足でプロセスが落ちた

OOM killer 発動時の対処 - メモリ不足でプロセスが落ちた

OOM killer とは何か?

OOM killer(Out-Of-Memory killer)はLinuxカーネルに組み込まれたメモリ管理機構で、物理メモリとスワップが枯渇した際にプロセスを強制終了してシステム全体のクラッシュを防ぐ。「突然プロセスが落ちた」「ログに何も残っていない」ときはまず OOM killer の発動を疑う。

症状のチェックポイント

  • アプリが理由不明でクラッシュした
  • systemctl statusexit-code=SIGKILL が表示される
  • dmesgOut of memory: Killed process の行がある

ログで OOM killer 発動を確認する方法

カーネルが OOM killer を発動すると、その記録は必ずカーネルリングバッファとシステムログに残る。最初にここを確認する。

dmesg で確認する

sudo dmesg | grep -i "out of memory"
sudo dmesg | grep -i "oom"
[1234567.890] Out of memory: Killed process 12345 (nginx) total-vm:512000kB, anon-rss:256000kB, file-rss:8000kB, shmem-rss:0kB, UID:0 pgtables:512kB oom_score_adj:0

journalctl でカーネルログを検索する

sudo journalctl -k --since "2 hours ago" | grep -i oom
sudo journalctl -k -g "Out of memory"

-k はカーネルメッセージのみ表示するオプション。時刻が絞れる場合は --since で範囲を狭める。

syslog / messages から確認する

sudo grep -i "out of memory" /var/log/syslog
sudo grep -i "oom_killer" /var/log/kern.log

dmesg のタイムスタンプはブート後秒数のため、dmesg -T を使うと人間が読める日時で表示される。

どのプロセスが殺されたか調べる

ログには殺されたプロセスの情報が詳細に記録される。

sudo dmesg | grep "Killed process"
[1234567.890] Killed process 12345 (nginx) total-vm:512000kB, anon-rss:256000kB

出力の読み方:

フィールド 説明
12345 プロセス PID
(nginx) プロセス名
total-vm 仮想メモリ確保量
anon-rss 実際に使用していた匿名ページ
file-rss ファイルバックドページ
oom_score_adj kill 優先度の調整値(0 がデフォルト)

OOM killer 発動前後のメモリ状態も記録される場合がある:

sudo dmesg | grep -A 30 "Out of memory" | head -50

OOM score の仕組みとスコア確認

カーネルは各プロセスに oom_score(0〜1000)を付けており、スコアが高いプロセスから順に kill 対象として選ぶ。スコアはメモリ使用量ベースで計算される。

現在のスコア確認

cat /proc/<PID>/oom_score

全プロセスのスコアをスコア降順で一覧する:

awk '{print $1}' /proc/*/status 2>/dev/null | \
  xargs -I{} sh -c 'echo "$(cat /proc/{}/oom_score 2>/dev/null) {} $(cat /proc/{}/comm 2>/dev/null)"' | \
  sort -rn | head -20

oom_score_adj でスコアを補正する

/proc/<PID>/oom_score_adj に -1000〜1000 の値を書き込むことでスコアを補正できる。

効果
-1000 OOM kill 対象から完全除外
-500 kill されにくくなる
0 デフォルト
500 kill されやすくなる
1000 最優先で kill される
# nginx プロセスを OOM kill 対象から外す(一時的)
echo -500 | sudo tee /proc/$(pgrep nginx)/oom_score_adj

-1000 に設定したプロセスは OOM killer に殺されなくなるため、そのプロセス自体がメモリリークを起こすとシステム全体がクラッシュするリスクがある。重要なシステムサービスに限定して使用すること。

即時対処:プロセス再起動とメモリ回収

OOM killer がプロセスを殺した直後は、システムのメモリが回復しているはずだが、根本原因が残っている場合は再発する。

殺されたサービスを再起動する

sudo systemctl restart <service-name>

ページキャッシュを手動解放する

sync
echo 3 | sudo tee /proc/sys/vm/drop_caches

大量メモリを使用しているプロセスを確認する

ps aux --sort=-%mem | head -20
free -h

再発防止 — oom_score_adj を永続化する

プロセスを再起動するたびに oom_score_adj はリセットされる。永続化するには systemd のユニットファイルに設定を追加する。

sudo systemctl edit <service-name>

エディタが開いたら以下を追記する:

[Service]
OOMScoreAdjust=-500

変更を反映する:

sudo systemctl daemon-reload
sudo systemctl restart <service-name>

確認:

cat /proc/$(pgrep <service>)/oom_score_adj

スワップを追加してメモリ圧迫を緩和する

スワップが不足または存在しない環境では、OOM killer が発動しやすくなる。スワップファイルを追加することで急激なメモリスパイク時のバッファを確保できる。

スワップファイルの作成手順

# 2GB のスワップファイルを作成する
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

有効化を確認する:

free -h
swapon --show
              total        used        free      shared  buff/cache   available
Mem:           3.8G        3.1G        100M         50M        600M        600M
Swap:          2.0G          0B        2.0G

再起動後も有効にする(/etc/fstab に追加)

echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

スワップは根本解決ではない

スワップはメモリ枯渇の緩衝材に過ぎない。アプリのメモリ使用量が持続的に増加し続けるならメモリリークの調査が必要。swappiness の値(デフォルト 60)も運用環境に応じて調整する(サーバーは 10〜20 が一般的)。

根本対策:メモリ使用量を見直す

OOM killer が繰り返し発動する場合、スコア調整やスワップ追加は対症療法に過ぎない。根本原因の調査が必要。

よくある根本原因

  1. メモリリーク — プロセスのメモリ使用量が時間とともに増加し続ける
  2. 設定値の過大なメモリ要求 — JVM の -Xmx、MySQL の innodb_buffer_pool_size 等が物理メモリを超えている
  3. 並列プロセス数の多すぎ — Apache/PHP-FPM の MaxRequestWorkers

メモリ使用量の経時変化を監視する

# 5秒間隔で特定プロセスのメモリ使用量を記録する
watch -n 5 "ps -p <PID> -o pid,rss,vsz,comm"

cgroup でメモリ上限を設定する(再発防止)

systemd 経由でサービスのメモリ上限を明示的に設定できる。

sudo systemctl edit <service-name>
[Service]
MemoryMax=512M
MemorySwapMax=0

上限を超えた場合、サービスのみが終了し他のプロセスへの影響を防げる。

次に読む