はじめに
Javaを書いていると「ヒープ」「スタック」という言葉をよく目にします。でも、実際のコードとどう対応しているのか、ぼんやりしていませんか?
この記事では 「newしたオブジェクトはどこに置かれるのか」「ローカル変数はどこにいるのか」 を、コード例と図解でサクッと整理します。
対象読者: Java実務1〜3年目で、メモリの話をちゃんと理解しておきたいエンジニア
1. まずは結論から
| 領域 | 何が置かれる? | ライフサイクル |
|---|---|---|
| スタック | ローカル変数、メソッド呼び出し情報 | メソッドが終わると自動で消える |
| ヒープ |
new で生成したオブジェクト |
GC(ガベージコレクション)が回収するまで残る |
ひとことで言えば、スタックは「作業台」、ヒープは「倉庫」 です。
2. コードで見てみよう
例1: 基本的なメソッド呼び出し
public class MemoryExample {
public static void main(String[] args) {
int count = 10; // ① スタックに積まれる
String message = new String("Hi"); // ② 参照はスタック、オブジェクトはヒープ
greet(message);
}
static void greet(String msg) { // ③ 新しいスタックフレームが積まれる
System.out.println(msg);
} // ④ greetのスタックフレームが消える
}
メモリの動き:
ポイントは message という変数そのものはスタックにいて、中身のStringオブジェクトはヒープにいるということです。変数が持っているのは「ヒープ上のオブジェクトの住所(参照)」にすぎません。
例2: スタックフレームの積み上がり
public class StackDemo {
public static void main(String[] args) {
int result = add(3, 5);
System.out.println(result);
}
static int add(int a, int b) {
int sum = a + b; // a, b, sum すべてスタック上
return sum;
}
}
add() が終わると、そのスタックフレームごと消えます。スタックは後入れ先出し(LIFO) なので、一番上のフレームから順に消えていく仕組みです。
例3: オブジェクトの参照が複数ある場合
public class ReferenceDemo {
public static void main(String[] args) {
List<String> listA = new ArrayList<>(); // ヒープにArrayListが1つ作られる
List<String> listB = listA; // 同じオブジェクトを指す参照がもう1つ
listB.add("Hello");
System.out.println(listA.size()); // → 1(同じオブジェクトだから)
}
}
listA と listB はスタック上の別の変数ですが、ヒープ上の同じオブジェクトを指しています。だから片方で add すると、もう片方にも反映される。これが参照の正体です。
3. プリミティブ型と参照型の違い
int x = 42; // 値そのものがスタックに入る
String s = "hello"; // 参照がスタックに入り、オブジェクトはヒープに置かれる
| 種類 | スタックに入るもの | 例 |
|---|---|---|
| プリミティブ型 | 値そのもの |
int, double, boolean など |
| 参照型 | ヒープ上のオブジェクトへの参照(アドレス) |
String, List, 自作クラスなど |
4. だから何? ― 実務で意味があること
「ヒープとスタックの違い、わかった。で、実務で何が嬉しいの?」という疑問にも答えておきます。
スタックオーバーフロー(StackOverflowError) は、メソッドの再帰呼び出しが深くなりすぎてスタックが溢れたときに起きます。
// これを実行すると StackOverflowError
static void infinite() {
infinite(); // スタックフレームが無限に積まれる
}
一方、OutOfMemoryError(ヒープ領域) は、ヒープにオブジェクトを作りすぎて溢れたときに起きます。
// これを実行すると OutOfMemoryError: Java heap space
List<byte[]> list = new ArrayList<>();
while (true) {
list.add(new byte[1024 * 1024]); // 1MBずつヒープを消費
}
つまり、エラーの種類を見ればどちらの領域が溢れたのか一発でわかるということです。トラブル対応の第一歩はここから始まります。
まとめ
- スタック = メソッドの作業台。ローカル変数や引数が置かれ、メソッド終了で自動的に片付く
-
ヒープ = オブジェクトの倉庫。
newで作ったものが置かれ、GCが掃除してくれる - 変数が持っているのは「参照(住所)」であって、オブジェクトそのものではない
-
StackOverflowError→ スタックの問題、OutOfMemoryError→ ヒープの問題
次回は、ヒープの掃除役である ガベージコレクション(GC) について解説します。
シリーズ記事
- 第1回: ヒープとスタック(この記事)
- 第2回: GC(ガベージコレクション)の基本(近日公開)
- 第3回: OutOfMemoryErrorが出たらどうする?(近日公開)