メモリ管理
所要時間: 35分
前提知識: free, vmstat コマンドを使ったこと がある
学べること:
- 仮想メモリの仕組み
- ページングとスワップ
- メモリの種類(RSS, VSZ, Shared)
- OOM Killer の動作
この章で答える疑問
「free の数字の意味は?」
「VSZ と RSS の違いは?」
「スワップって何?」
「OOM Killer って何?なぜプロセスが突然死ぬ?」
1. 仮想メモリとは
1.1 なぜ仮想メモリが必要か
┌─────────────────────────────────────────────────────────────────────┐
│ 仮想メモリがない世界 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 問題1: メモリ不足 │
│ ├── 物理メモリ 16GB に 20GB 分のプログラムは動かない │
│ │
│ 問題2: メモリの断片化 │
│ ├── プログラム A, B, C が起動・終了を繰り返すと │
│ └── 連続した空き領域がなくなる │
│ │
│ 物理メモリ: │
│ ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │
│ │ A │ │ B │ │ C │ │ A │ │ B │ │ │
│ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ │
│ ↑ ↑ ↑ ↑ ↑ │
│ 空き 空き 空き 空き 空き(断片化) │
│ │
│ 問題3: セキュリティ │
│ ├── プログラム A がプログラム B のメモリを読める │
│ └── 悪意のあるプログラムがパスワードを盗める │
│ │
└─────────────────────────────────────────────────────────────────────┘
1.2 仮想メモリの仕組み
┌─────────────────────────────────────────────────────────────────────┐
│ 仮想メモリの概念 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 各プロセスは「自分専用の巨大なメモリ空間」を持っているように見える │
│ │
│ プ ロセスA プロセスB │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 仮想アドレス空間 │ │ 仮想アドレス空間 │ │
│ │ │ │ │ │
│ │ 0x0000 │ │ 0x0000 │ ← 同じアドレスでも │
│ │ ... │ │ ... │ 別の物理メモリ │
│ │ 0x7FFF... │ │ 0x7FFF... │ │
│ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │
│ │ ページテーブル │ ページテーブル │
│ │ (変換表) │ (変換表) │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 物理メモリ │ │
│ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ │ │
│ │ │ A │ │ B │ │ A │ │共有│ │ B │ │空き│ │ │
│ │ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────── ──────────────────────────────────────────────────┘
1.3 アドレス変換
┌─────────────────────────────────────────────────────────────────────┐
│ 仮想アドレス → 物理アドレス変換 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ CPU │
│ │ │
│ │ 仮想アドレス: 0x00401000 │
│ ▼ │
│ ┌───────────────────────────────────────┐ │
│ │ MMU (Memory Management Unit) │ │
│ │ │ │
│ │ ページテーブルを参照して変換 │ │
│ │ │ │
│ │ 仮想ページ番号: 0x00401 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌──────────────────────────────────┐ │ │
│ │ │ ページテーブル │ │ │
│ │ ├──────────────────────────────────┤ │ │
│ │ │ 仮想 │ 物理 │ フラグ │ │ │
│ │ ├──────────┼───────────┼───────────┤ │ │
│ │ │ 0x00401 │ 0x12345 │ RW- │ │ │
│ │ │ 0x00402 │ 0x67890 │ R-- │ │ │
│ │ │ ... │ ... │ ... │ │ │
│ │ └──────────────────────────────────┘ │ │
│ │ │ │
│ └───────────────────────────────────────┘ │
│ │ │
│ │ 物理アドレス: 0x12345000 │
│ ▼ │
│ 物理メモリ │
│ │
└─────────────────────────────────────────────────────────────────────┘
2. ページングとスワップ
2.1 ページとは
┌─────────────────────────────────────────────────────────────────────┐
│ ページの概念 │
├────────────────────────────────────────────────────────────── ───────┤
│ │
│ メモリは「ページ」という固定サイズのブロックで管理される │
│ │
│ 典型的なページサイズ: 4KB (x86/x64) │
│ │
│ 仮想メモリ 物理メモリ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ ページ0 (4KB) │ ────────────→│ フレーム 5 │ │
│ ├─────────────────┤ ├─────────────────┤ │
│ │ ページ1 (4KB) │ ────────────→│ フレーム 12 │ │
│ ├─────────────────┤ ├─────────────────┤ │
│ │ ページ2 (4KB) │ ──(未割当) │ フレーム 8 │ │
│ ├─────────────────┤ ├─────────────────┤ │
│ │ ページ3 (4KB) │ ────────────→│ フレーム 3 │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ ページ = 仮想メモリのブロック │
│ フレーム = 物理メモリのブロック │
│ │
└──────────────────────────────────────────────────────────────── ─────┘
2.2 ページフォルト
┌─────────────────────────────────────────────────────────────────────┐
│ ページフォルトの流れ │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. プログラムがメモリアドレス 0x12345678 にアクセス │
│ │ │
│ ▼ │
│ 2. MMU がページテーブルを確認 │
│ │ │
│ ├── 物理メモリにある → 通常のアクセス │
│ │ │
│ └── 物理メモリにない → ページフォルト発生! │
│ │ │
│ ▼ │
│ 3. カーネルが処理を引き 継ぐ │
│ │ │
│ ├── 正当なアクセスの場合: │
│ │ ├── ディスクからデータを読み込み(スワップイン) │
│ │ ├── または新しいページを割り当て │
│ │ └── プログラムを再開 │
│ │ │
│ └── 不正なアクセスの場合: │
│ └── Segmentation Fault(セグフォ)→ プロセス終了 │
│ │
└─────────────────────────────────────────────────────────────────────┘
2.3 スワップ
┌─────────────────────────────────────────────────────────────────────┐
│ スワップの仕組み │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 物理メモリが足りなくなったら、使用頻度の低いページをディスクへ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 物理メモリ (16GB) │ │
│ │ │ │
│ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ← 全部使用中 │ │
│ │ │ A │ │ B │ │ C │ │ D │ │ E │ │ F │ │ │
│ │ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ │ │
│ │ │ │
│ └──────────────────────────── ─────────────────────────────────┘ │
│ │
│ 新しいメモリが必要! │
│ │
│ スワップアウト(物理メモリ → ディスク) │
│ ┌───┐ │
│ │ C │ ──────────────────────────────────────┐ │
│ └───┘ │ │
│ ↓ │ │
│ 空きになる ▼ │
│ ┌─────────────────────────────┐ │
│ │ スワップ領域 (ディスク) │ │
│ │ │ │
│ │ ┌───┐ ┌───┐ ┌───┐ │ │
│ │ │ C │ │...│ │...│ │ │
│ │ └───┘ └───┘ └───┘ │ │
│ │ │ │
│ └─────────────────────────────┘ │
│ │
│ ※ ディスクはメモリより約1000倍遅い → スワップが多発すると性能低下 │
│ │
└──────────────────────────────────────────── ─────────────────────────┘
2.4 Linux での確認
# スワップの状態確認
$ free -h
total used free shared buff/cache available
Mem: 15Gi 8.2Gi 2.1Gi 512Mi 5.0Gi 6.2Gi
Swap: 8.0Gi 1.2Gi 6.8Gi
# スワップ詳細
$ swapon --show
NAME TYPE SIZE USED PRIO
/swap.img file 8G 1.2G -2
# vmstat でスワップ活動を監視
$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 1228800 2150400 102400 5120000 0 0 0 10 100 500 10 5 85 0 0
1 0 1228800 2150400 102400 5120000 0 0 0 0 120 600 12 6 82 0 0
# si = swap in (ディスク → メモリ)
# so = swap out (メモリ → ディスク)
# これらが大きいと性能問題
# ページフォルト確認
$ cat /proc/[pid]/stat | awk '{print "minor faults:", $10, "major faults:", $12}'
minor faults: 1234567 major faults: 89
# minor fault: メモリ内で解決(軽い)
# major fault: ディスクアクセスが必要(重い)
3. メモリの種類と free コマンド
3.1 free の出力を読む
$ free -h
total used free shared buff/cache available
Mem: 15Gi 8.2Gi 2.1Gi 512Mi 5.0Gi 6.2Gi
Swap: 8.0Gi 1.2Gi 6.8Gi
┌─────────────────────────────────────────────────────────────────────┐
│ free コマンドの各フィールド │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ total : 物理メモリの総量 (15GB) │
│ │
│ used : 使用中のメモリ (8.2GB) │
│ ├── プロセスが使用 │
│ └── カーネルが使用 │
│ │
│ free : 完全に空いているメモリ (2.1GB) │
│ ※ これが少なくても問題ない場合がある │
│ │
│ shared : 共有メモリ (512MB) │
│ └── tmpfs, 共有ライブラリなど │
│ │
│ buff/cache: バッファとキャッシュ (5.0GB) │
│ ├── buffer: ディスクI/O用の一時領域 │
│ └── cache: ファイルの内容をキャッシュ │
│ ※ 必要に応じて解放される │
│ │
│ available : 新しいプロセスが使えるメモリ (6.2GB) │
│ └── free + 解放可能なbuff/cache │
│ ★ これを見るのが重要! │
│ │
└─────────────────────────────────────────────────────────────────────┘
3.2 よくある誤解
┌─────────────────────────────────────────────────────────────────────┐
│ 「free が少ない!」は問題か? │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ❌ 間違い: free が 2GB しかない → メモリ不足! │
│ │
│ ✅ 正解: available を確認 │
│ │
│ Linux は空きメモリを無駄にしない │
│ ├── 空いているメモリはファイルキャッシュに使う │
│ ├── 新しいプロセスが必要になったらキャッシュを解放 │
│ └── だから free が少なくても available があれば OK │
│ │
│ 本当に問題なのは: │
│ ├── available が少ない │
│ ├── swap が増え続けている │
│ └── OOM Killer が発動 │
│ │
└─────────────────────────────────────────────────────────────────────┘
3.3 VSZ と RSS
$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
java 1234 5.0 8.5 4568792 1397624 ? Sl 10:30 15:23 java -jar app.jar
┌─────────────────────────────────────────────────────────────────────┐
│ VSZ と RSS の違い │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ VSZ (Virtual Size): 4,568,792 KB ≈ 4.3GB │
│ ───────────────── │
│ プロセスの仮想アドレス空間の合計 │
│ ├── 実際に物理メモリを使っているわけではない │
│ ├── 「予約」されているだけのメモリも含む │
│ └── 共有ライブラリの全体も含む(重複カウント) │
│ │
│ RSS (Resident Set Size): 1,397,624 KB ≈ 1.3GB │
│ ──────────────────────── │
│ 実際に物理メモリに存在するサイズ │
│ ├── プロセスが今使っている物理メモリ │
│ ├── 共有ライブラリの共有部分も含む(重複カウント) │
│ └── こちらの方が実態に近い │
│ │
│ ┌──────────────────────────────────────── ─────────────────────┐ │
│ │ 仮想アドレス空間 (VSZ: 4.3GB) │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ 物理メモリにある (RSS: 1.3GB) │ │ │
│ │ │ ┌────────┐ ┌────────┐ ┌────────┐ │ │ │
│ │ │ │ヒープ │ │スタック │ │共有lib │ │ │ │
│ │ │ │(使用中) │ │ │ │ │ │ │ │
│ │ │ └────────┘ └────────┘ └────────┘ │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ 物理メモリにない(スワップ or 未割当) │ │ │
│ │ │ ┌────────────┐ ┌────────────────────────────────┐ │ │ │
│ │ │ │スワップ中 │ │まだ使っていない予約領域 │ │ │ │
│ │ │ └────────────┘ └────────────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
3.4 PSS と USS
より正確なメモリ使用量を知りたい場合:
# smaps で詳細確認
$ cat /proc/[pid]/smaps_rollup
Rss: 1397624 kB
Pss: 987432 kB
Shared_Clean: 234567 kB
Shared_Dirty: 8901 kB
Private_Clean: 123456 kB
Private_Dirty: 567890 kB
┌─────────────────────────────────────────────────────────────────────┐
│ メモリ測定の種類 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ USS (Unique Set Size): │
│ ├── そのプロセス固有のメモリ │
│ └── Private_Clean + Private_Dirty │
│ │
│ PSS (Proportional Set Size): │
│ ├── 共有メモリを共有しているプロセス数で割った値 │
│ ├── 例: 3プロセスで共有する 300KB → 各プロセスに 100KB として計上 │
│ └── システム全体のメモリ使用量を把握するのに最適 │
│ │
│ RSS (Resident Set Size): │
│ ├── 共有メモリを全額カウント │
│ └── 複数プロセスの RSS を足すと実際より大きくなる │
│ │
│ 正確さ: USS < PSS < RSS │
│ │
└─────────────────────────────────────────────────────────────────────┘
4. OOM Killer
4.1 OOM Killer とは
┌─────────────────────────────────────────────────────────────────────┐
│ OOM Killer の役割 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ OOM = Out Of Memory │
│ │
│ システムがメモリ不足になったとき: │
│ ├── スワップも使い果たした │
│ ├── 新しいメモリを確保できない │
│ └── システム全体がフリーズする危険 │
│ │
│ → カーネルの OOM Killer が「犠牲者」を選んで強制終了 │
│ │
│ 目的: システム全体のクラッシュを防ぐ │
│ │
└─────────────────────────────────────────────────────────────────────┘
4.2 犠牲者の選び方
┌─────────────────────────────────────────────────────────────────────┐
│ OOM Score の計算 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 各プロセスに「OOM スコア」が付けられる(0〜1000) │
│ スコアが高いほど kill されやすい │
│ │
│ スコアを上げる要因: │
│ ├── メモリ使用量が大きい │
│ ├── 子プロセスのメモリ使用量 │
│ └── 実行時間が短い(長く動いているプロセスは保護される傾向) │
│ │
│ スコアを下げる要因: │
│ ├── root 権限で動いている │
│ ├── ハードウェアに直接アクセスしている │
│ └── oom_score_adj で調整されている │
│ │
│ 確認: │
│ $ cat /proc/[pid]/oom_score │
│ 250 │
│ │
│ $ cat /proc/[pid]/oom_score_adj # -1000〜1000 で調整可能 │
│ 0 │
│ │
└─────────────────────────────────────────────────────────────────────┘
4.3 OOM Killer の発動を確認
# dmesg で OOM Killer のログを確認
$ dmesg | grep -i "out of memory"
[12345.678901] Out of memory: Kill process 5678 (java) score 850 or sacrifice child
[12345.678902] Killed process 5678 (java) total-vm:4568792kB, anon-rss:1234567kB
# journalctl でも確認可能
$ journalctl -k | grep -i oom
Jan 01 12:34:56 server kernel: java invoked oom-killer: gfp_mask=0x...