Javaは「一度書けばどこでも動く (Write Once, Run Anywhere)」というコンセプトで誕生し、オブジェクト指向プログラミングと自動メモリ管理(ガベージコレクション)を特徴としています。C/C++と比べてメモリ管理の負担が少ないため、多くのエンジニアが重宝している言語ですが、“どのようにメモリが管理されているのか” を深く理解している人は意外に多くありません。
しかし、Javaのメモリ管理の仕組みを理解しておくと、以下のような利点があります。
- アプリケーションがメモリ不足に陥ったときの原因を素早く特定できる
- ガベージコレクションのチューニング方法を理解し、パフォーマンス改善に活かせる
- クラスローディングの仕組みを把握し、複雑なアプリケーション構成でもクラスパス問題や依存関係のトラブルに対処できる

このページでは、Javaが提供する「スタック」「ヒープ」「メソッド領域(メタスペース)」などのメモリ領域と、その背後で動作するクラスローディングやガベージコレクションなどの仕組みを、なるべく噛み砕いて解説します。
Javaのメモリ領域の全体像
Javaアプリケーションが動作している最中、JVMで使用するメモリは大きく以下の領域に分けて使われています。
- スタック (Stack)
- ヒープ (Heap)
- メソッド領域 (Method Area) / メタスペース (Metaspace)
- (広義の)Static領域
それぞれ役割や管理方法が異なり、理解することでJavaプログラムの動きを俯瞰できるようになります。
- OSメモリ = “オフィスビル全体の空間”
たとえば、ビルの各フロアはさまざまな企業や部署が借りる可能性があるし、共有スペースも存在します。コンピュータ全体では、OS(オペレーティングシステム)がこの「ビルオーナー」のような立場です。ビル全体のスペースを区画に分け、各アプリケーション(プロセス)に「ここを使っていいよ」という形で割り当てています。 - JVMメモリ = “ビル内でJavaが借りているフロア”
Javaプログラムを動かすためのJVMは、ビル(OSメモリ)の一部フロアを借りてオフィスを構えます。もちろん、そのフロアをどれだけ借りられるか(どれだけメモリを使用できるか)は、OSの許可や設定(-Xmsや-Xmxなど)によって決まります。つまり、「ビル全体の中の、Java専用のオフィス空間」がJVMメモリというイメージです。
スタック (Stack)
用途:
- メソッド呼び出しごとのローカル変数や演算途中の一時値を格納する場所
- メソッド開始時に「スタックフレーム」を積み、終了時に破棄される (LIFO - Last In First Out)
特徴:
- 高速アクセス: push/popによる領域の確保・解放が簡単。
- メモリサイズに制限:
-Xss
オプションなどでスレッドごとに割り当てるスタックサイズを設定可能。再帰が深すぎるとStackOverflowError
を起こすことがある。 - スレッド単位で分離: 各スレッドは自前のスタックを持つため、あるスレッドのスタックオーバーフローが他スレッドに直接影響するわけではない。
スタックはメソッドの引数やローカル変数だけでなく、バイトコードの実行時に使う演算用の一時領域(オペランドスタック)も含まれます。メソッドが終了するたびにフレームが破棄される仕組みは、「スコープを抜ければ変数が消える」というローカル変数の振る舞いそのものです。
ヒープ (Heap)
用途:
new
キーワードで生成されるオブジェクト本体や配列を配置する領域- ほぼすべてのオブジェクトはヒープに置かれ、ガベージコレクションの対象となる
特徴:
- ガベージコレクション(GC) によって不要オブジェクトが自動回収される
-Xms
や-Xmx
オプションなどで初期サイズや最大サイズを設定できる- 世代別に分割される(Young世代・Old世代 など)ことで効率的にGCを行う仕組みがある
Javaのオブジェクトはすべてヒープに置かれ、その参照をスタック上などで持つのが基本パターンです。もしスタックから参照が切れてしまうと、いずれGCによって回収され、プログラマは手動でfree()
やdelete
を呼び出す必要がありません。この仕組みがJavaのメモリ管理を大幅に楽にしています。
Javaが使うメモリ空間(JVMメモリ)の中でも、「ヒープ (Heap)」と「スタック (Stack)」は、利用シーンや管理方式がまったく異なります。オフィスフロアに置き換えて考えてみましょう。
ヒープ = “フロアの倉庫や共有ロッカー”
スタック = “デスクの引き出し”
メソッド領域 (Method Area) / メタスペース (Metaspace)
用途:
- JVMがロードしたクラス情報やメソッド定義、定数プール、JITコンパイルされたコードなどを格納
- Java 8以降ではネイティブメモリを使用する “Metaspace” が導入され、以前存在したPermGen(Permanent Generation)は廃止された
特徴:
- クラスファイルを読み込み、バイトコードを内部的に解析してできた「クラスのメタデータ」を保持
- Metaspaceはデフォルトで拡張可能。
-XX:MetaspaceSize
などのオプションで管理する - クラスローダがアンロードされる(不要になれば)クラスも破棄されるが、通常の開発ではあまり頻繁にアンロードされないケースが多い
Java 7以前のPermGenでは「クラス情報」だけでなく「staticフィールドの実体も置かれる」といった説明がされることも多かったですが、Java 8以降は基本的にクラスメタデータをMetaspaceへ置く方針に変わっています。静的フィールド自体はヒープに置かれる実装が一般的です。
(広義の) Static領域
C/C++とは異なり、Javaでは「static領域」という名前の明確な領域は仕様上存在しません。しかし以下のようなものを“Static領域”とまとめて呼ぶことがあります。
static
フィールド(クラス変数): クラスに1つだけ存在し、インスタンス化しなくてもアクセスできるフィールドstatic
メソッド: インスタンスに依存せず、クラス名から直接呼び出せるメソッド
この静的な要素がクラスロード時にどこへ格納されるかはJVMの実装依存です。しばしば「Method Areaに格納される」として説明されますが、実装によっては「staticフィールドはヒープ上に確保され、メタデータはMetaspaceに持つ」というケースが多いです。大まかに「クラスの初期化タイミングで確保されるクラス変数」くらいに理解しておくとよいでしょう。
Javaプログラムの実行の流れを「メモリ」に焦点を当てて
ここからは、コンパイル後の .class
ファイルがどのように読み込まれ、メモリに配置され、実際のメソッド呼び出しに至るまでの流れを順を追って見ていきます。

これまで説明してきたそれぞれのメモリがどのように利用されるのか?に焦点を当てて確認していきましょう。
1 クラスファイルのロード
- クラスローダの探索
- Javaプログラムが「あるクラスを使うぞ」となったとき、まず「そのクラスは既にロード済みか?」を確認します。ロードされていなければ、ClassLoaderがクラスパスやJARファイルから
.class
を探します。
- Javaプログラムが「あるクラスを使うぞ」となったとき、まず「そのクラスは既にロード済みか?」を確認します。ロードされていなければ、ClassLoaderがクラスパスやJARファイルから
- クラスファイルの読み込み (バイナリ取得)
- 見つかった
.class
ファイルのバイナリデータを読み込む。 - この時点では、ファイル内容を単純にバイト列として保持しているだけです。
- 見つかった
2 メタスペースへの格納とリンク
- クラスファイルの解析 (ロード・リンク)
.class
のバイナリを解釈し、クラスのメタ情報(フィールド定義、メソッド定義、定数プールなど)を JVM 内部で扱いやすい構造体に変換します。- ここでバイトコード検証や外部クラス参照の解決などが行われ、クラスとして適切に利用可能かチェックされます。
- メタスペース(Metaspace) への配置
- 解析した結果生まれるクラスメタデータがMetaspaceに格納されます。
- Java 8以降では、PermGenの代わりにMetaspaceが使われ、ネイティブメモリ上に確保されるため、必要に応じてメモリを拡張できる仕組みになっています。
3 クラス初期化 (static領域)
- クラス初期化条件
- ロードが完了しただけでは「クラスは未初期化」の状態です。
- 実際に
new
されたりstatic
メンバに初アクセスするときに、初期化処理がトリガーされます。
- staticフィールドのメモリ確保 & 初期化子の実行
static
変数を保持する領域が確保され、static { ... }
で定義された初期化処理が呼ばれます。- フィールドの実体はヒープに確保され、クラスメタデータはMetaspaceにある、という実装が多いです。
4 メソッド呼び出しとスタックフレーム
- メソッドのバイトコード取得 & 実行
- あるクラスのメソッドが呼び出されると、JVMはまずMetaspace内の「メソッドのバイトコード情報」を参照します。
- Interpreter(またはJITコンパイラ)を通じて、このバイトコードが実行されます。
- スタックフレーム生成
- スレッドごとのJavaスタックに、新たなフレームが積まれます。
- メソッド内で宣言されるローカル変数や引数、演算用の一時値はこのフレームに格納される。
- ヒープ上のオブジェクト参照
- メソッド内で
new
を呼ぶと、ヒープからオブジェクト用のメモリが割り当てられる。 - スタック側には、そのオブジェクトの参照(ポインタ)が置かれる。
- メソッド内で
- メソッド終了時にスタックフレーム破棄
return
などでメソッドを抜けると、対応するスタックフレームがポップされて破棄される。- ヒープ上のオブジェクトは参照が残っていれば生存し続ける。