メモリレイアウト
プログラムのメモリ空間の構造で、スタック、ヒープ、コード領域、データ領域から構成される
メモリレイアウトとは
プログラムのメモリ空間は、コード領域、データ領域、ヒープ、スタックの 4 つの領域で構成される。JavaScript 開発者が直接操作することは少ないが、パフォーマンスやメモリリークの理解に重要。
メモリ領域
メモリ領域を図で示す。
高アドレス ┌──────────────┐
│ スタック ↓ │ ← 関数の引数、ローカル変数
│ │
│ ↓ 成長方向 │
│ │
│ ↑ 成長方向 │
│ │
│ ヒープ ↑ │ ← 動的に確保されるオブジェクト
├──────────────┤
│ データ領域 │ ← グローバル変数、定数
├──────────────┤
│ コード領域 │ ← 実行可能なコード
低アドレス └──────────────┘
| 領域 | 内容 | 管理 |
|---|---|---|
| スタック | 関数の引数、ローカル変数 | 自動 (LIFO) |
| ヒープ | オブジェクト、配列、文字列 | GC or 手動 |
| データ | グローバル変数、定数 | コンパイル時に確定 |
| コード | 実行可能な命令 | 読み取り専用 |
スタック vs ヒープ
スタックとヒープの違いを以下にまとめる。
| 観点 | スタック | ヒープ |
|---|---|---|
| 速度 | 高速 (ポインタ移動のみ) | 低速 (アロケーション) |
| サイズ | 小さい (数 MB) | 大きい (数 GB) |
| 管理 | 自動 (関数終了で解放) | GC or 手動 |
| 格納データ | プリミティブ、参照 | オブジェクト、配列 |
V8 (Node.js) のメモリ
V8 (Node.js) のメモリのコード例を示す。
// スタック: プリミティブ値と参照
const x = 42; // スタックに 42 を格納
const obj = { a: 1 }; // スタックに参照、ヒープにオブジェクト
// ヒープ: オブジェクト
// V8 のヒープは Young Generation と Old Generation に分かれる
Lambda の MemorySize はヒープ、スタック、OS のオーバーヘッドを含む総量で、128 MB から 10,240 MB の範囲で設定する。CPU はこの設定量に比例して割り当てられ、公式ドキュメントの記載では 1,769 MB で 1 vCPU 相当になる。したがって 1,769 MB より下の領域ではメモリを増やすほど計算自体が速くなるが、それを超えた分は 2 つ目以降の vCPU として渡されるため、並列化していないコードでは増やしても速くならない。メモリ設定は実行時間あたりの単価に直接効くので、実際の関数で数点測り、所要時間と課金額の釣り合う値を選ぶ。
スタックオーバーフロー
スタックオーバーフローのコード例を示す。
// ❌ 無限再帰 → スタックオーバーフロー
function infinite(): number { return infinite(); }
// RangeError: Maximum call stack size exceeded
// ✅ 末尾呼び出しの形 (ただし V8 は最適化しないのでループの方が確実)
function factorial(n: number, acc = 1): number {
if (n <= 1) return acc;
return factorial(n - 1, n * acc);
}
末尾再帰は「関数の最後の処理が自分自身の呼び出しだけ」という形で、理屈の上ではスタックを積み増さずに実行できる。ただし V8 (Node.js・Chrome) はこの最適化を実装していないため、上の factorial も引数を大きくすればスタックを使い切る。Node.js v26 で 100000 を渡すと RangeError: Maximum call stack size exceeded になり、素朴な再帰で辿れる深さは 1 万段前後だった (上限は環境と関数のフレームサイズで変わる)。再帰の深さが入力次第で伸びる処理は、書き方を工夫するのではなく while ループか明示的なスタック配列へ書き換えるのが確実である。
メモリリークの検出
メモリリークの検出の例を示す。
# Node.js のヒープ使用量を確認
node -e "console.log(process.memoryUsage())"
# { rss: 47988736, heapTotal: 6176768, heapUsed: 3847112, external: 1421601, arrayBuffers: 14573 }
返る値はすべてバイト単位の数値なので、人が読むときは 1024 で 2 回割る。rss はプロセス全体が OS から借りている物理メモリ、heapTotal は V8 がヒープとして確保した枠、heapUsed はそのうち実際に使われている分である。リークの疑いを見るときは 1 回の値ではなく推移を追う。負荷をかけた後に heapUsed が下がりきらず rss も戻らないなら、参照が残って回収されていないオブジェクトを疑う。
メモリレイアウトについては関連書籍でも詳しく扱われている。
この記事は役に立ちましたか?
関連用語
ガベージコレクション
不要になったメモリを自動的に解放するランタイムの仕組み
プロセスとスレッドの違いとは - メモリ空間 / 生成コスト / 通信方法を比較
プロセスとスレッドの違いをメモリ空間 / 生成コスト / 通信方法の 3 軸で比較。Node.js での使い分け、Lambda の実行環境、データ競合とコンテナの PID 1 の落とし穴まで解説
システムコール
ユーザー空間のプログラムが OS カーネルの機能を呼び出すインターフェース
メモ化
関数の計算結果をキャッシュし、同じ引数での再計算を避ける最適化手法
低レイヤ
OS / メモリ / CPU などハードウェアに近い領域。コンピュータの動作原理を支える
Iterator パターン
コレクションの内部構造を公開せずに要素へ順番にアクセスする手段を提供するデザインパターン。JavaScript の Iterator プロトコル / Generator / Async Iterator と遅延評価の落とし穴を解説