1. 概要

String オブジェクトは、Java言語で最もよく使用されるクラスです。

このクイック記事では、Java文字列プール—文字列がJVMによって格納される特別なメモリ領域について説明します。

2. 文字列インターン

JavaでのStringsの不変性のおかげで、JVMは、プールに各リテラル文字列のコピーを1つだけ格納することにより、それらに割り当てられるメモリの量を最適化できます。 このプロセスはインターニングと呼ばれます。

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. 文字列リテラルと文字列オブジェクト

new()演算子を使用して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

同様に、 Stringリテラルをnew()演算子を使用して==演算子を使用して作成された String オブジェクトと比較すると、falseが返されます。

String fifth = "Baeldung";
String sixth = new String("Baeldung");
System.out.println(fifth == sixth); // False

一般に、可能な場合は文字列リテラル表記を使用する必要があります。 読みやすく、コンパイラーにコードを最適化する機会を与えます。

5. 手動インターン

インターンするオブジェクトでintern()メソッドを呼び出すことにより、Java文字列プールで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 をインターンするリスクは、JVMからOutOfMemoryエラーが発生する可能性があることです。 文字列が多すぎます。

Java 7以降、Java文字列プールはガベージコレクションであるヒープスペースに格納されます JVMによるこのアプローチの利点は、 OutOfMemoryエラーのリスクを軽減参照されていないため文字列プールから削除され、メモリが解放されます。

7. パフォーマンスと最適化

Java 6では、実行できる唯一の最適化は、 MaxPermSizeJVMオプションを使用したプログラム呼び出し中にPermGenスペースを増やすことです。

-XX:MaxPermSize=1G

Java 7には、プールサイズを調べて拡張/縮小するためのより詳細なオプションがあります。 プールサイズを表示するための2つのオプションを見てみましょう。

-XX:+PrintFlagsFinal
-XX:+PrintStringTableStatistics

バケットの観点からプールサイズを増やしたい場合は、 StringTableSizeJVMオプションを使用できます。

-XX:StringTableSize=4901

Java 7u40より前は、デフォルトのプールサイズは1009バケットでしたが、この値は、最近のJavaバージョンでいくつか変更される可能性があります。 正確には、Java7u40からJava11までのデフォルトのプールサイズは60013でしたが、現在は65536に増加しています。

プールサイズを増やすとメモリの消費量が増えますが、Stringsをテーブルに挿入するのに必要な時間が短縮されるという利点があることに注意してください。

8. Java9に関する注意

Java 8までは、文字列は内部的に文字の配列として表されていました– char [] UTF-16 でエンコードされているため、すべての文字は2バイトのメモリー。

Java 9では、次のような新しい表現が提供されます。 コンパクトストリング。 この新しいフォーマットは、適切なエンコーディングを選択します char []バイト[] 保存されているコンテンツによって異なります。

新しいString表現は必要な場合にのみUTF-16エンコーディングを使用するため、ヒープメモリの量が大幅に少なくなり、[X]が少なくなります。 X194X]JVMのガベージコレクターオーバーヘッド。

9. 結論

このガイドでは、JVMとJavaコンパイラがJava文字列プールを介してStringオブジェクトのメモリ割り当てを最適化する方法を示しました。

この記事で使用されているすべてのコードサンプルは、GitHubから入手できます。