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で利用可能]です。