目次
「freeを見たらメモリの空きがほとんどない。増設しないとまずいのでは」と慌てた経験はないでしょうか。あるいは、夜間バッチが原因不明で落ちていて、ログをたどるとKilledとだけ出ていた、ということもあるかもしれません。Linuxのメモリの見え方は直感に反する部分が多く、正しく読めないと不要な増設をしたり、逆に本当の不足を見逃したりします。
この記事では、freeやtopで誤解しやすいbuff/cacheとavailableの意味、swapの本当の役割とswappinessの考え方、OOM Killerがプロセスを選ぶ仕組みとログの読み方、そしてDockerやKubernetesのメモリ制限との関係までを解説します。CPUやディスクを含む監視の基本はLinuxサーバーのパフォーマンス監視入門にあるので、本記事はメモリに絞って掘り下げます。
freeコマンドの正しい読み方|buff/cacheとavailable
「空きが少ない」は正常な状態
まずfree -mの出力を見てみましょう。
$ free -m
total used free shared buff/cache available
Mem: 7822 2140 310 120 5372 5230
Swap: 2047 0 2047
free列が310MBしかないのを見て「危険」と判断するのは誤りです。Linuxは使われていない物理メモリを遊ばせず、ディスクから読んだファイルの内容をページキャッシュとして保持します。これがbuff/cacheの正体で、同じファイルを再度読むときにディスクアクセスを省くための「有効活用」です。アプリがメモリを必要とすればカーネルは即座にキャッシュを解放して割り当てるので、キャッシュは実質的に「空き」に近い扱いになります。
見るべきはavailable
実務で見るべき列はavailableです。これは「解放可能なキャッシュを含めて、新しいアプリがswapを使わずに確保できるメモリの見積もり」で、上の例なら約5.2GBが使える計算になります。監視で閾値を設定するなら「availableがtotalの10〜20%を下回ったら警告」のように、availableを基準にしてください。topでも同じ意味の「avail Mem」が表示されます。なお、キャッシュのうちshared(tmpfsや共有メモリ)は解放できないため、/dev/shmに大量のファイルを置いている環境では注意が必要です。
# プロセス別のメモリ使用量(RSS)上位を確認
ps aux --sort=-rss | head -n 10
# より正確な実使用量(PSS:共有ライブラリを按分した値)
sudo smem -r -k | head # smemパッケージが必要
# /proc/meminfoで内訳を確認
grep -E 'MemTotal|MemAvailable|Cached|Shmem|SwapTotal|SwapFree' /proc/meminfo
swapの役割とswappinessの考え方
swapは「遅いメモリ」ではなく安全弁
swapはディスク上に確保した領域で、物理メモリが逼迫したときに、長く使われていないメモリページを退避させる場所です。「swapを使い始めたら遅くなる」というのは事実ですが、swapがまったくない場合、メモリが尽きた瞬間にOOM Killer(後述)がプロセスを強制終了するしかなくなります。少量のswapは「急激な使用増で即死する」のを防ぐ安全弁として機能し、長時間アイドルなページを退避してページキャッシュに回す効果もあります。
クラウドのインスタンスはswapなしで起動することが多いため、必要なら明示的に作成します。
# 2GBのswapファイルを作成して有効化
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
# 現在のswap使用状況
swapon --show
vmstat 1 5 # si/so列(swap in/out)が継続的に0以外なら逼迫のサイン
swappinessの調整
vm.swappinessは「ページキャッシュを削るのと、プロセスのメモリをswapに追い出すのと、どちらを優先するか」の傾向値で、既定値は60です。0に近いほどswapを避け、100に近いほど積極的にswapします。DBサーバーのように「アプリのメモリは絶対に追い出したくない」用途では1〜10程度に下げるのが一般的で、逆にデスクトップや雑多なサービスが同居するサーバーでは既定値のままでも問題ありません。sysctl -w vm.swappiness=10で即時変更でき、/etc/sysctl.d/に書いて永続化します。
OOM Killerの仕組みとログの読み方
誰が殺されるのか
物理メモリもswapも尽きると、カーネルはシステム全体の停止を避けるためOOM Killer(Out Of Memory Killer)を発動し、プロセスを1つ選んでSIGKILLで強制終了します。選ばれるのはoom_scoreが最も高いプロセスで、基本的には「使用メモリが最も多いプロセス」です。つまり、原因がメモリリークしている小さなスクリプトであっても、殺されるのはメモリを多く使っているDBやアプリ本体、というのが典型です。
OOM Killerの発動はdmesgやジャーナルに記録されます。ログの探し方を知っていれば、「原因不明で落ちた」の大半はここで判明します。
# OOM Killerの記録を探す
sudo dmesg -T | grep -i -E 'out of memory|oom-kill|killed process'
sudo journalctl -k --since "yesterday" | grep -i oom
# 出力例
# [Tue Sep 2 03:12:41 2026] Out of memory: Killed process 1842 (mysqld)
# total-vm:3985412kB, anon-rss:2710244kB, file-rss:0kB, shmem-rss:0kB
# 各プロセスのスコアを確認
cat /proc/1842/oom_score
cat /proc/1842/oom_score_adj
守りたいプロセスにはoom_score_adjを設定する
oom_score_adjは-1000〜1000の範囲で優先度を補正する値で、-1000にするとそのプロセスはOOM Killerの対象から外れます。DBのような「絶対に落ちてほしくない」プロセスに負の値を、逆にバッチのような「落ちても再実行できる」プロセスに正の値を設定しておくと、いざというときに犠牲になる順番を制御できます。systemdで管理しているサービスなら、ユニットファイルにOOMScoreAdjust=-900と書くのが確実です。ただし-1000で完全に保護するとカーネルが他のすべてを殺し尽くしても解決せずシステムごと固まる可能性があるため、-900程度にとどめるのが無難です。
トラブル事例:夜間バッチの時間だけDBが落ちる
症状:毎晩3時ごろにMySQLが停止し、systemdが自動再起動する。日中は安定している。
原因:3時に起動するレポート集計スクリプト(Python)が、大きなCSVを丸ごとメモリに読み込んで数GBを消費していた。物理メモリが尽きた時点でOOM Killerが発動し、スコアが最も高いmysqldが選ばれていた。スクリプト自体は完走していたため、担当者はバッチが原因だと気づいていなかった。
対処:dmesgで発動時刻とkilled processを確認し、原因のスクリプトを特定。スクリプトを行単位のストリーム処理に書き換えてピーク使用量を抑え、MySQLのユニットにOOMScoreAdjust=-900、バッチにはsystemd-run -p OOMScoreAdjust=500で実行するようにして、再発時の犠牲順も明確にしました。
コンテナのメモリ制限との関係
コンテナのOOMはホストのOOMとは別
Dockerの--memoryやKubernetesのresources.limits.memoryは、cgroupという仕組みでプロセスグループ単位のメモリ上限を設けます。上限に達するとホスト全体には余裕があってもそのコンテナ内でOOM Killerが動き、コンテナは終了コード137(128+SIGKILLの9)で停止します。docker inspectでOOMKilled: true、Kubernetesではkubectl describe podにReason: OOMKilledと表示されます。Docker Composeの本番運用やKubernetesで「原因不明の再起動」が起きたら、まずこのフラグを確認してください。
# コンテナがOOMで落ちたかを確認
docker inspect --format '{{.State.OOMKilled}} exit={{.State.ExitCode}}' myapp
# メモリ上限と現在の使用量
docker stats --no-stream myapp
# Kubernetesの場合
kubectl describe pod myapp-7d9f | grep -A3 'Last State'
ランタイムがコンテナの上限を知らない問題
Node.jsやJavaはヒープサイズの既定値を「ホストの物理メモリ」から決めることがあり、コンテナの上限を超えて確保しようとして殺されるケースがあります。Node.jsなら--max-old-space-size(MB単位)で上限の7〜8割を指定し、Javaは-XX:MaxRAMPercentage=75のようにコンテナ上限に対する割合で指定するのが定石です。また、コンテナの上限にはページキャッシュも含まれるため、大量のファイルI/Oをするコンテナは想定より早く上限に達することも覚えておいてください。
アプリ側でできる対策
増設の前に確認すること
free -mのavailableとvmstatのsi/soで、本当に逼迫しているかを確認する(キャッシュが多いだけなら問題なし)ps aux --sort=-rssで、どのプロセスが多く使っているかを把握する- 時間とともにRSSが増え続けるプロセスがあればメモリリークを疑い、言語ごとのプロファイラで確認する
- ワーカー数×1ワーカーのメモリが物理メモリを超えていないか計算する(Gunicorn・PHP-FPM・Puma等で頻出)
- それでも足りなければ増設、またはメモリを多く使う処理を別サーバーやジョブキューに分離する
特に4番目は見落とされがちで、「アクセスが増えたときだけ落ちる」原因の多くは、ワーカー数の設定がメモリ容量に見合っていないことです。1ワーカーの実使用量を計測して、余裕を持った数に調整してください。
まとめ
Linuxのメモリは「空き(free)が少ない」のが正常で、判断基準はavailableです。swapは遅いメモリではなく即死を防ぐ安全弁であり、swappinessで傾向を調整します。OOM Killerは最もメモリを使うプロセスを殺すため、原因と犠牲者が一致しないことを前提にdmesgで発動記録を確認し、oom_score_adjで守るべきプロセスを指定しておきましょう。コンテナでは終了コード137とOOMKilledフラグを確認し、ランタイムのヒープ上限をコンテナの上限に合わせて設定します。まずは本番サーバーでfree -mとdmesg | grep -i oomを実行し、現状を把握するところから始めてください。
サーバーの安定運用やリソース設計にお困りの際は、Harmonic Societyのシステム開発・インフラ支援にご相談ください。
Harmonic Society
この記事の内容、自社の業務でも活かせそうですか?
ローカルLLM・AI・クラウドなどの技術導入を、要件整理からPoC・社内展開まで代表エンジニアが伴走します。オンライン対応・全国OK。まずは30分の無料相談から。売り込みはしません。
関連記事
Related / 9 articles
Notes & Insights
- プログラミング
DDoS攻撃の仕組みと対策入門|レイヤー別の防御とCDN・クラウドの活用
DDoS攻撃をボリューム型・プロトコル型・アプリ層に分けて仕組みを解説し、自前サーバーで防げない理由、CloudflareやAWS Shieldの標準防御、オリジンIPの隠し方、レートリミットとBot対策、攻撃を受けたときの初動、費用が跳ね上がるDenial of Walletへの備えまでわかります。
- プログラミング
WAFとは?仕組み・導入パターン・誤検知対策|Webアプリを攻撃から守る実践ガイド
WAFがファイアウォールやIDSと何が違うのか、シグネチャとマネージドルールの仕組み、Cloudflare WAF・AWS WAF・ModSecurityの比較、フォーム送信がブロックされる誤検知の調査と例外設定、ログ監視、WAFが代替できないことまで実践的に解説します。
- プログラミング
セキュリティヘッダー入門|CSP・HSTS・X-Frame-Optionsの設定と効果を実践解説
CSP・HSTS・X-Frame-Options・X-Content-Type-Optionsなど主要セキュリティヘッダーが防ぐ攻撃と、CSPのReport-Onlyからの段階導入、nonce/hash、HSTS preloadの不可逆リスク、Nginx・Next.js・Astroでの設定例、確認方法を解説します。
- プログラミング
クラウドの通信費(Egress)入門|データ転送量課金の仕組みと転送コストを抑える設計
クラウドの「受信無料・送信有料」の原則、AZ間・リージョン間・インターネット向けの単価差、NATゲートウェイ処理料の罠、CDNで転送量を減らす方法、バックアップやログ転送の見落とし、請求書で転送料を特定する手順を解説。想定外の請求を防げます。
- プログラミング
秘密情報をGitに入れない仕組み|.gitignore・git-secrets・履歴から漏れた鍵の削除
APIキーや.envをGitにコミットしてしまう典型経路と、.gitignore・.env.exampleの運用、pre-commitでのgitleaks検知、GitHub secret scanningの活用、漏れた鍵の無効化と履歴書き換え(git filter-repo)の手順を解説。仕組みで再発を防げます。
- プログラミング
開発・ステージング・本番環境の分離設計|環境差分をなくす構成とアクセス制御
開発・ステージング・本番それぞれの目的と、構成をコードで揃える方法、環境別の設定注入、本番データを使わないテストデータ戦略、ステージングの保護(Basic認証・IP制限・noindex)、コストを抑える運用までを解説。環境差分による本番障害を防げます。
- プログラミング
ngrok・Cloudflare Tunnelでローカルを公開|Webhook開発とデモ環境の作り方
NAT内のローカル環境にStripeやLINEのWebhookを届けるトンネリングの仕組みを解説。ngrok・Cloudflare Tunnel・localtunnelの比較、固定ドメインと認証、リクエスト検査、公開時のセキュリティ、自宅サーバー公開への応用までわかります。
- プログラミング
ローカル開発環境のHTTPS化|mkcert・hostsファイル・自己署名証明書の正しい使い方
ローカル開発をHTTPS前提にすべき理由(Secure Cookie・Service Worker・OAuth)と、mkcertでローカルCAを作りhostsで独自ドメインを割り当ててVite・Next.js・Dockerで使う手順を解説。証明書警告を無視する癖の危険も理解できます。
- プログラミング
localhost・0.0.0.0・127.0.0.1の違い|ポートとUnixソケットを理解して「つながらない」を解決
localhost・127.0.0.1・0.0.0.0の意味の違い、Dockerで外から接続できない原因、host.docker.internal、ポート競合の調べ方、Unixソケットの利点と権限、1024未満ポートの制約を解説。「つながらない」を仕組みから解決できます。
Harmonic Society
「読んで終わり」にせず、自社の業務で試してみませんか?
AI・ローカルLLM・クラウドの導入を、要件整理からPoC・社内展開まで代表エンジニアが伴走します。オンライン対応・全国OK・売り込みなし。
無料・30分・オンラインOK|1営業日以内に返信します