javaのメモリ領域
ざっくり!
- レジストリ
- cpuに存在領域で、制御できない
- stack領域
- 基本型、参照値が保存される
- メモリ管理は、ポイントを前後に移動することで行う
- heap領域
- newで生成されたオブジェクト
- static領域
- 言葉のとおり、staticメンバー
- constant pool
- コンパイル時確定され、.classに存在するデータ + ほか
- 非RAM領域
- hdd等
Stringとheap領域とconstant pool
本題:
「コンパイル時確定され、.classに存在するデータ」について検証してみたい。
よく知ってる内容の検証
constant poolに生成される
String a1 = "aaa";
String a2 = "aaa";
System.out.println(a1 == a2); // true
- contant pool領域に"aaa"が生成され、stack領域に"aaa"を指す参照値a1が生成される。
- contant pool領域に"aaa"があるので、stack領域に"aaa"を指す参照値a2が生成される。
- a1,a2とも"aaa"のアドレスが入っているので、trueになる。
heap領域に生成される
String a3 = new String("aaa");
String a4 = new String("aaa");
System.out.println(a3 == a4); // false
- heap領域に"aaa"が生成され、stack領域に"aaa"を指す参照値a3が生成される。
- heap領域に"aaa"が生成され、stack領域に"aaa"を指す参照値a4が生成される。
- a3,a4は別々の"aaa"アドレスが入っているので、falseになる。
constant pool, heap領域に生成される
String a5 = "aaa";
String a6 = new String("aaa");
System.out.println(a5 == a6); // false
- constant pool領域に"aaa"が生成され、stack領域に"aaa"を指す参照値a5が生成される。
- heap領域に"aaa"が生成され、stack領域に"aaa"を指す参照値a6が生成される。
- a5,a6は別々の"aaa"アドレスが入っているので、falseになる。
ちょっと変わった内容の検証
定数 + 定数
String a1 = "ab";
String a2 = "a" + "b";
System.out.println(a1 == a2); // true
- constant pool領域に"ab"が生成され、stack領域に"ab"を指す参照値a1が生成される。
- "a"は定数、"b"は定数、よってコンパイル時にa2 = "ab"になる。
constant pollに存在する"ab"を指す参照値a2が生成される。 - a1,a2とも"ab"のアドレスが入っているので、trueになる。
- decompileで覗いてみると,
String a2 = "ab";
だった。
定数 + new String("")
String a1 = "ab";
String a2 = "a" + new String("b");
System.out.println(a1 == a2); // false
- constant pool領域に"ab"が生成され、stack領域に"ab"を指す参照値a1が生成される。
- new String("")はコンパイル時には確定されない。
よって、実行時にheap領域に"b"が生成、さらにheap領域"ab"が生成される。 - 結果、a1,a2は別々の"ab"アドレスが入っているので、falseになる。
定数 + 参照値
String a1 = "ab";
String a2 = "b";
String a3 = "a" + a2;
System.out.println(a1 == a3); // false
- constant pool領域に"ab"が生成され、stack領域に"ab"を指す参照値a1が生成される。
- constant pool領域に"b"が生成され、stack領域に"ab"を指す参照値a2が生成される。
- a2は参照値なので、コンパイル時には確定されない。
よって、実行時にheap領域に"ab"が生成、a3が指す。 - 結果、a1はconstant pool,a3はheap領域なので、falseになる。
定数 + final
String a1 = "ab";
final String a2 = "b";
String a3 = "a" + a2;
System.out.println(a1 == a3); // true
- finalを付けると、a2は定数と見なされ、a3は定数 + 定数 = 定数になる。
decompilerで見ると、下記にコンパイルされた。
String a1 = "ab";
String a2 = "b";
String a3 = "ab";
System.out.println(a1 == a3);
String.intern()
constant poolを拡張できるメソッド。
Stringインスタンス.intern()した場合、
- 同じ値がconstant poolに存在する場合、その参照値を返す。
- 同じ値がconstant poolに存在しない場合、constant poolに値を生成し、その参照値を返す。
String a1 = "ab";
String a2 = new String("ab");
System.out.println(a1 == a2); // false
a2 = a2.intern();
System.out.println(a1 == a2); // true
- constant pool領域に"ab"が生成され、stack領域に"ab"を指す参照値a1が生成される。
- heap領域に"ab"が生成され、stack領域に"ab"を指す参照値a2が生成される。
- falseになるのは略。
- a2.intern()かつ"ab"がcontant poolに存在するので、constant poolの"ab"アドレスをa2に返す。
- a1, a2ともcontant poolの"ab"を指すので、trueになる。
おまけ:Stringの+がパフォーマンスがよくない原因
String s = null;
for(int i = 0; i < 100; ++i) {
s = s + "a";
}
今までの検証を元に、
s + "a";
のsは変数なので、実行する度に新しいオブジェクトが生成と破棄が行われからだね。