クラッシュの構造
本番サーバーが順調に稼働している最中、突然すべてが停止することがあります。警告も正常なシャットダウンもなく、ただプロセスが死ぬのです。dmesg を通じてカーネルログを調べると、次のような悪名高いログ行が見つかります。
Out of memory: Kill process 4821 (java) score 512 or sacrifice child
Killed process 4821 (java) total-vm:2048000kB, anon-rss:1024000kB
これは Linux カーネルの OOM (Out Of Memory) Killer が作動した状態です。これは冷徹な安全装置です。物理 RAM が完全に使い果たされると、カーネルは選択を迫られます。システム全体をロックアップさせるか、単一のプロセスを強制終了してメモリを回収するかです。カーネルは常に後者を選択します。
ヒットリスト:カーネルはどのように犠牲者を選ぶのか
Linux はどのプロセスを排除するかを決定するためにスコアリングシステムを使用します。実行中のすべてのタスクには oom_score が割り当てられます。スコアが高いほど、最初にターゲットとなります。このアルゴリズムは通常、大量の RAM を消費しつつ、システムの核心的な安定性には不可欠ではないプロセスを探し出します。
Java アプリケーションは、小規模なクラウドインスタンスで頻繁に犠牲になります。例えば、2GB の RAM を搭載した月額10ドルの VPS を運用している場合、1.5GB のヒープで構成された JVM は、OS が動作するための余裕をほとんど残しません。2GB の制限に達すると、カーネルは巨大な Java プロセスを検知し、引き金を引きます。
ステップ 1: OOM イベントの確認
推測に頼ってはいけません。システムログから「killed」のシグネチャを検索し、実際に OOM Killer が原因であったことを確認してください。
# 最近のイベントを確認するためにカーネルバッファをチェックする
dmesg -T | grep -i oom
# Ubuntu/Debian で過去のログを検索する
grep -i 'killed process' /var/log/syslog
# CentOS/RHEL で過去のログを検索する
grep -i 'killed process' /var/log/messages
ステップ 2: 即時の緩和策
方法 A: スワップによるセーフティネットの構築
多くの現代的なクラウドプロバイダー(AWS や DigitalOcean など)は、スワップスペースがゼロの状態でインスタンスを提供します。RAM は高速ですが、有限です。スワップはオーバーフロータンクとして機能します。アプリケーションが速くなるわけではありませんが、RAM の使用率が 100% に達した瞬間に OOM Killer が発動するのを防ぐことができます。
すぐに 2GB のスワップファイルを作成するには、以下の手順に従ってください。
# 2GB のファイルを作成する
sudo fallocate -l 2G /swapfile
# ファイルの権限を保護する
sudo chmod 600 /swapfile
# スワップ領域を初期化する
sudo mkswap /swapfile
# スワップを有効にする
sudo swapon /swapfile
# 再起動後も設定を維持するようにする
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
方法 B: Java ヒープ制限の適切なサイジング
Java が犠牲になった場合、-Xmx (Max Heap) の設定が攻めすぎている可能性があります。4GB の RAM を搭載したサーバーで -Xmx4g を設定するのは間違いです。JVM はスレッドスタックやネイティブコードのために追加のメモリを必要とし、OS が安定して動作するには少なくとも 512MB が必要です。
オーバーヘッドを残すように起動スクリプトを調整します。
# 例: 2GB RAM のサーバーでは、ヒープを 1.2GB に制限する
java -Xms512m -Xmx1280m -jar app.jar
ステップ 3: 高度なチューニング
方法 C: 重要なサービスの保護
データベースや監視エージェントのように、何があっても存続させなければならない重要なプロセスがある場合があります。その場合、OOM のヒットリストにおける優先順位を手動で下げることができます。スコアの範囲は -1000 から 1000 です。値を -1000 に設定すると、そのプロセスは実質的に「強制終了不可」になります。
# 重要なサービスの PID を取得する
pidof my_database_app
# 調整値を設定する (例: PID 1234)
echo -1000 > /proc/1234/oom_score_adj
これを使用する際は注意が必要です。カーネルが最大のプロセスを強制終了できない場合、システムが最終的にハングアップするまで、他の小さなプロセスをすべて強制終了し始めます。
方法 D: より厳格なオーバーコミットポリシー
デフォルトでは、Linux は楽観的です。プロセスが一度にすべてを使用しないと想定し、実際に存在する量よりも多くのメモリを要求することを許可します。オーバーコミットの動作を変更することで、カーネルをより現実的な設定に強制できます。
# オーバーコミットを「オーバーコミットしない」(モード 2) に設定する
sudo sysctl -w vm.overcommit_memory=2
sudo sysctl -w vm.overcommit_ratio=80
これによりメモリ割り当ての予測可能性が高まりますが、後でカーネルによって強制終了される代わりに、malloc() 呼び出し中にアプリケーションが「Out of Memory」エラーで失敗する可能性があります。
プロアクティブな監視
目標は、カーネルが対処する前にメモリの問題を修正することです。リソースを常に監視しましょう。
- アラート通知: RAM の使用率が 5 分間以上 90% を超えた場合に、Slack やメールで通知を飛ばします。
- リーク検出:
topやhtopを使用してメモリの増加を追跡します。アプリの RSS (Resident Set Size) が一度も下がることなく毎日上昇している場合は、メモリリークが発生しています。 - Docker の分離: コンテナを実行する場合は、常に
docker-compose.ymlでハードリミットを設定してください。これにより、1 つのリークしているコンテナがホスト全体を道連れにするのを防げます。
検証
スワップを追加したりヒープを調整したりしたら、新しい制限を確認してください。
# アクティブなメモリとスワップの合計を確認する
free -h
# メモリ使用率をリアルタイムで監視する
watch -n 5 free -h
今後 48 時間、ログを監視してください。dmesg の出力がクリーンなままであれば、サーバーには安定を維持するための十分な余裕があるということです。

