C
OS/メモリ/Lesson 03

メモリ管理 — 仮想メモリ・ページング・アロケーション

45分·theory

メモリ管理 — 仮想メモリ・ページング・アロケーション

🎯 このレッスンを読み終えたら

このレッスンを読み終えると、次の3つを自信を持って説明できるようになります。

  • ✅ Stack / Heap / Code / Data セグメント
  • ✅ ページングとセグメンテーション + 仮想メモリ
  • ✅ ページフォルト + LRU ページ置換

学習目標をチェックリストとして手元に置き、すべて答えられるようになったらレッスンを閉じてください。

仮想メモリ — *無限メモリの幻想*

一言で言うと: 各プロセスがすべてのメモリを自分のものとして見ているかのような幻想。実際には OS がページ単位で物理 RAM にマッピングしています。

なぜ必要なのか:

理由意味
分離プロセス A がプロセス B のメモリにアクセスできない(セキュリティと安定性)
抽象化0x00000000 から始まる統一されたアドレス空間(開発をシンプルに)
オーバーコミット16 GB RAM で 64 GB の仮想メモリを使用可能(ページ単位でオンデマンドロード)
スワップ使っていないページをディスクへ(RAM をより効率的に活用)

変数アクセスの6ステップ (例: int x = 5; printf("%d", x);):
1. 仮想アドレス&x = 0x7fff5fbff8ac (スタック領域)
2. MMU 変換 — Memory Management Unit がページテーブルを参照
3. TLB キャッシュ — よく使うマッピングは CPU キャッシュに(1サイクルのヒット)
4. 物理アドレス — 0x7fff5fbff8ac → 物理 RAM 0x12345678
5. キャッシュ確認 — L1 → L2 → L3 → RAM (4 → 12 → 40 → 200 サイクル)
6. 値の返却5 がレジスタへ

> 💡 ページフォルト = ページテーブルにマッピングなし → OS がディスクからロード → 非常に遅い(数 ms)。

ページング — 4 KB 単位のメモリ管理

ページ = 4 KB (Linux 標準)。仮想・物理メモリともにページ単位で管理。

ページテーブルの構造:

  • プロセスごとに独立したページテーブル
  • 仮想ページ番号 → 物理ページ番号 + 権限 (r/w/x)
  • 64bit システム = 4段階ページテーブル (PML4 → PDPT → PD → PT)

TLB (Translation Lookaside Buffer):

  • CPU 内のページマッピングキャッシュ(通常 64〜1024 エントリ)
  • ヒット時は 1 サイクル、ミス時はページテーブルウォーク(10〜100 サイクル)

ページングの落とし穴:

問題原因解決策
ページフォルトマッピングなし → ディスクからロードよく使うデータは LRU キャッシュに
スラッシングページフォルトが多すぎる → CPU がディスク待ちになるRAM を増やす・ワーキングセットを縮小
メモリ断片化小さな空き領域が散在するBuddy/Slab アロケータ(カーネルが処理)
OOM KillerRAM + スワップが枯渇 → OS がプロセスを強制終了oom_score を低く保ち・メモリリークを修正

ヒュージページ (2 MB・1 GB):

  • 大量メモリを扱う DB や JVM で使用
  • TLB ミス減少 → パフォーマンス向上
  • Linux: echo 1024 > /proc/sys/vm/nr_hugepages

メモリリーク + OOM デバッグ

メモリリーク: 確保したメモリを解放しない → 時間とともに蓄積 → 最終的に OOM 発生。

言語別のリスク:

言語リスク理由
C/C++非常に高いmalloc 後に free を呼ばなければ即座にリークが発生
Java/Python/Go低い (GC あり)ただし参照を保持し続けると GC が回収できない(リスナー・キャッシュ・グローバル変数)
Rustほぼなし所有権システムがコンパイル時に安全性を保証

リーク診断ツール:

  • Linux: valgrind --leak-check=full ./app (C/C++)
  • Java: jmap -heap <PID>jhat または VisualVM
  • Python: tracemalloc モジュールまたは objgraph
  • Node.js: --inspect + Chrome DevTools Heap Snapshot
  • 全言語共通: top または htop で RSS が増え続ける場合はリークを疑う

OOM Killer の動作:
1. RAM + スワップがほぼ枯渇
2. カーネルが oom_score を計算(メモリ使用量・優先度に基づく)
3. 最もスコアの高いプロセスに SIGKILL を送信
4. /var/log/syslog または dmesg に記録 (Out of memory: Kill process ...)

> 💡 本番サーバーでは vm.swappiness=10(スワップ最小化)とメモリ監視アラートの設定が必須です。

💻 📌 シナリオで学ぶメモリ分析
# ============================================================
# シナリオ 1: "サーバーのメモリが不足しているというアラートが来た"
# ============================================================
# システム全体のステータスから — 人が読みやすいように
free -h
#               total   used   free  shared  buff/cache   available
# Mem:          15Gi    11Gi   1.2Gi  200Mi   3.0Gi       3.5Gi
# Swap:         2.0Gi   1.5Gi  500Mi
#
# ポイント:
#   available  = *実際に新しく使える* メモリ (used ではなくこれを見るべき)
#   buff/cache = ディスクキャッシュ (必要に応じて自動回収)
#   swap used > 0 = RAM 不足の兆候 ⚠️

# さらに詳しく (50+ 項目)
cat /proc/meminfo

# 1秒間隔のトレンドを見る
vmstat 1 5                    # 5回出力
# free·si(swap in)·so(swap out) カラムに注目

# ============================================================
# シナリオ 2: "どのプロセスがメモリを多く使っているか?"
# ============================================================
ps aux --sort=-%mem | head -10
# RSS カラム = 実際に使用しているメモリ (KB)
# VSZ カラム = 仮想メモリ (参考用)

# カラーインタラクティブ
top -o %MEM                   # M キーでソートも可能

# 特定のプロセスの *メモリマップ* 詳細
pmap -x 28391                 # ライブラリ・ヒープ・スタック別の使用量
cat /proc/28391/status | grep -E 'VmRSS|VmSize|VmSwap'
#   VmRSS = 実際の物理メモリ (RAM)
#   VmSize = 仮想メモリ (予約されたアドレス空間)
#   VmSwap = スワップされた量

# ============================================================
# シナリオ 3: "OOM Killer が私のプロセスを強制終了した"
# ============================================================
# 1) OOM イベントの確認
dmesg | grep -i 'out of memory'
sudo journalctl -k | grep -i oom
# 出力例: Out of memory: Kill process 28391 (java) score 854 or sacrifice child

# 2) プロセスの OOM スコアの確認 (高いほど先に強制終了される)
cat /proc/28391/oom_score
# 0~1000. メモリ使用量・優先度に基づく

# 3) 重要なプロセスの保護
echo -1000 > /proc/28391/oom_score_adj  # 絶対に強制終了させない (root のみ)
# または systemd ユニットファイルに:
#   OOMScoreAdjust=-500

# 4) スワップの調整 (サーバーでは通常低くする)
cat /proc/sys/vm/swappiness          # デフォルト 60
sudo sysctl vm.swappiness=10         # production 推奨 (スワップ最小化)

# ============================================================
# シナリオ 4: "Java アプリケーションがメモリリークの疑いがある"
# ============================================================
# 1) ヒープ統計を一行で
jmap -heap 28391
# Heap Configuration:
#   MaxHeapSize = 4096 MB
# Heap Usage:
#   PS Young Generation: Eden 256/256MB (100% used)  ← 危険
#   PS Old Generation:   2800/3072 MB (91% used)     ← 非常に危険

# 2) GC 統計を1秒ごとに (長期トレンドを見る)
jstat -gcutil 28391 1s
# S0  S1  E    O    M    YGC  YGCT  FGC  FGCT  GCT
# 0   85  92   91   95   1820 25.5  234  120   145
# FGC (Full GC) が頻繁に発生・時間が長い場合 = リークの疑い

# 3) ヒープダンプ (分析用)
jmap -dump:live,format=b,file=/tmp/heap.bin 28391

# 4) Eclipse MAT または VisualVM で開いて分析
# - Leak Suspects レポート
# - どのクラスがメモリを占有しているか

# ============================================================
# シナリオ 5: "Python アプリケーションが時間とともに RSS が増加する"
# ============================================================
# tracemalloc — メモリ割り当ての追跡 (コード内に挿入)
# import tracemalloc
# tracemalloc.start()
# ... コード実行 ...
# snapshot = tracemalloc.take_snapshot()
# for stat in snapshot.statistics('lineno')[:10]:
#     print(stat)
# 出力: /app/main.py:42: size=125 MB ...

# memory-profiler — 行単位のメモリ測定
# pip install memory-profiler
# python -m memory_profiler app.py

# ============================================================
# シナリオ 6: "Huge Page の有効化による性能改善"
# ============================================================
# 大きなメモリを扱う DB・JVM で TLB ミスを削減
cat /proc/sys/vm/nr_hugepages         # 現在 (通常 0)
echo 1024 | sudo tee /proc/sys/vm/nr_hugepages   # 1024 × 2MB = 2GB
# 永続的な適用: /etc/sysctl.conf に vm.nr_hugepages=1024

🤖 AI にはこう依頼してみよう

このレッスンの概念を理解していれば、AI に具体的な指示を出せるようになります。漠然とした「直して」ではなく、語彙のある依頼 — それがトークン節約の出発点です。

  • 「このコードのメモリ使用パターン (heap/stack) を分析して」
  • 「このプロセスの RSS / Virtual Memory を計測するコマンドを教えて」

なぜこれがトークンを減らすのか

概念を知らないと、AI の回答を受け取っても「それって何ですか?」と再質問しなければなりません。その「再質問」がトークンを消費します。概念を一度理解しておけば、会話が一度で完結します。

メモリ管理 — 仮想メモリ・ページング・アロケーション - OS