Java String 定数プール

Java String がメモリに保存される方式

String がメモリに保存される方式

Java で String を生成する方法は 2 つある。

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 レベルの性能に低下するとされている。

参考