Java String 定数プール
String がメモリに保存される方式
Java で String を生成する方法は 2 つある。
new演算子を使う方式- リテラル(Literal)を使う方式
2 つの宣言方式の違いは次のとおりである。
new 宣言 |
リテラル宣言 | |
|---|---|---|
| 保存場所 | “Heap” 領域に保存 | “String constant pool” に保存 |
| 速度 | Literal 宣言より遅い | new 宣言より速い |
| 同じ文字列の比較 | .equals メソッドで比較すると同じ結果になる |
== 演算子で比較できる |
“String constant pool” に文字列が存在すればそのアドレス値を返し、存在しなければ新しく保存して新しいアドレス値を返す。 string constant pool は Perm 領域に存在していたが、Java 7 を境に位置が変更された。
同じ文字列を上記 2 つの方式で生成し、== 演算子と .equals() で比較すると、次のような結果になる。
public static void main(String[] args) {
String a = "apple";
String b = "apple";
String c = new String("apple");
System.out.println(a == b); // true
System.out.println(a == c); // false
System.out.println(a.equals(b)); // true
System.out.println(a.equals(c)); // true
}
String constant pool の位置変更
Java 6 まで String constant pool の位置は Perm 領域だった。Perm 領域にあったものが Java 7 で Heap 領域に変更された。その理由は OutOfMemoryException の問題である。
Perm 領域は固定サイズであり、Runtime にサイズが拡張されない。Perm 領域のサイズを増やすことはできるが、いずれにしても Runtime にサイズが変わるわけではない。そのため Java 6 までは、String の intern() メソッドを呼び出すと OutOfMemoryException が発生する可能性があり、その部分を制御できなかったため、ほとんど使用しないのが妥当だった。
そこで Oracle のエンジニアは、Java 7 で string constant pool の位置を Perm 領域ではなく Heap 領域へ変更した。Heap 領域へ変更することで得られる利点は何だろうか。それは、string constant pool のすべての文字列も GC の対象になれるという点である。
String constant pool のサイズは -xx:StringTableSize オプションで指定できる。ここには 1,000,000 のような数字ではなく、1,000,003 のような素数を使う必要がある。これは hashCode 性能に関係する部分であり、Java Performance Tuning Guide の記事に詳しい内容がある。
intern() メソッドを積極的に使うなら、-xx:StringTableSize のデフォルト値(1009)より高く設定する必要がある。そうしないと、Linked List レベルの性能に低下するとされている。