Java文字列プールのガイド
1概要
String
オブジェクトは、Java言語で最も使用されているクラスです。
この簡単な記事では、Java String Pool – ** StringがJVMによって格納される特別なメモリ領域 – について探ります。
2文字列のインターナリング
Javaでは
Strings
が不変であるため、JVMは
各リテラル
String
のコピーを1つだけ
プールに格納することによって、それらに割り当てられるメモリ量を最適化できます。このプロセスは
interning
と呼ばれます。
String
変数を作成してそれに値を代入すると、JVMは同じ値の
String
をプールから検索します。
-
見つかった場合、Javaコンパイラは追加のメモリを割り当てずに、単にそのメモリアドレスへの参照を返します。
見つからない場合は、プールに追加され(内部)、その参照が返されます。
これを検証するための小さなテストを書きましょう。
String constantString1 = "Baeldung";
String constantString2 = "Baeldung";
assertThat(constantString1)
.isSameAs(constantString2);
3
Strings
コンストラクタを使って割り当てられます
new
演算子を介して
String
を作成すると、Javaコンパイラは新しいオブジェクトを作成し、それをJVM用に予約されているヒープスペースに格納します。
このように作成されたすべての
String
は、独自のアドレスを持つ異なるメモリ領域を指します。
これが前のケースとどう違うのかを見てみましょう。
String constantString = "Baeldung";
String newString = new String("Baeldung");
assertThat(constantString).isNotSameAs(newString);
4
String
リテラルと
Stringオブジェクト
-
new()
演算子を使用して
String
オブジェクトを作成すると、常にヒープメモリ内に新しいオブジェクトが作成されます。一方、
String
リテラル構文を使用してオブジェクトを作成すると、 “ Baeldung”は、それがすでに存在する場合、Stringプールから既存のオブジェクトを返すかもしれません。** そうでなければ、新しいStringオブジェクトを作成し、将来の再利用のために文字列プールに入れます。
高レベルでは、両方とも
String
オブジェクトですが、主な違いは
new()
演算子が常に新しい
String
オブジェクトを作成するという点にあります。また、リテラルを使用して
String
を作成すると、それがインターンされます。
String
リテラルと
new
演算子を使用して作成された2つの
String
オブジェクトを比較すると、これははるかに明確になります。
String first = "Baeldung";
String second = "Baeldung";
System.out.println(first == second);//True
この例では、
String
オブジェクトは同じ参照を持ちます。
次に、
new
を使用して2つの異なるオブジェクトを作成し、それらが異なる参照を持っていることを確認しましょう。
String third = new String("Baeldung");
String fourth = new String("Baeldung");
System.out.println(third == fourth);//False
同様に、==演算子を使用して
new()
演算子を使用して作成された
String
オブジェクトを
String
リテラルと比較すると、
false:
が返されます。
String fifth = "Baeldung";
String sixth = new String("Baeldung");
System.out.println(fifth == sixth);//False
一般的に、可能な限り
String
リテラル表記を使用するべきです。
読みやすく、コンパイラにコードを最適化する機会を与えます。
5手動インターナリング
インターンを設定したいオブジェクトで
intern()
メソッドを呼び出すことによって、JavaのStringプールに手動で
String
をインターンすることができます。
String
を手動でインターナリングすると、その参照がプールに格納され、JVMは必要に応じてこの参照を返します。
このためのテストケースを作成しましょう:
String constantString = "interned Baeldung";
String newString = new String("interned Baeldung");
assertThat(constantString).isNotSameAs(newString);
String internedString = newString.intern();
assertThat(constantString)
.isSameAs(internedString);
6. ガベージコレクション
Java 7より前のバージョンでは、JVMはJava文字列プールを固定サイズの
PermGen
スペースに配置しました。実行時に拡張することはできず、ガベージコレクションには適していません** 。
PermGen
に(
Heap
の代わりに)
Strings
を埋め込むことのリスクは、あまりにも多くの
Strings
を埋め込むと、
JVMから
OutOfMemory
error
を受け取る可能性があることです。
Java 7以降、Java文字列プールは** JVMによってガベージコレクションされた
Heap
スペースに格納されています。これにより、メモリが解放されます。
7. パフォーマンスと最適化
Java 6では、私たちが実行できる唯一の最適化は、
MaxPermSize
JVMオプションを使用したプログラム呼び出し中に
PermGen
スペースを増やすことです。
-XX:MaxPermSize=1G
Java 7では、プールサイズを調べたり拡張/縮小するためのより詳細なオプションがあります。プールサイズを表示するための2つのオプションを見てみましょう。
-XX:+PrintFlagsFinal
-XX:+PrintStringTableStatistics
デフォルトのプールサイズは1009です。プールサイズを増やす場合は、
StringTableSize
JVMオプションを使用できます。
-XX:StringTableSize=4901
-
プールサイズを増やすとより多くのメモリが消費されますが、テーブルに
Strings
を挿入するのに必要な時間が短縮されるという利点があります。
8 Java 9
に関するメモ
Java 8までは、
Strings
は内部的に
UTF-16
でエンコードされた
char[]
の文字の配列として表されていたので、すべての文字は2バイトのメモリを使用します。
Java 9では、__Compact Stringsと呼ばれる新しい表現が提供されています。
新しい
String
表現は必要なときにのみ
UTF-16
エンコーディングを使用するため、
ヒープメモリの量は大幅に少なくなり、その結果として
JVMの
Garbage Collector
オーバーヘッドが少なくなります。
9結論
このガイドでは、JVMとJavaコンパイラが、Java String Poolを介して
String
オブジェクトのメモリ割り当てを最適化する方法を示しました。
この記事で使用されているすべてのコードサンプルはhttps://github.com/eugenp/tutorials/tree/master/java-strings[Githubで利用可能]です。