Java文字列プールのガイド

Java文字列プールのガイド

1. 概要

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

この簡単な記事では、Java文字列プール—the special memory region where Strings are stored by the JVMについて説明します。

2. ストリングインターン

JavaでのStringsの不変性のおかげで、JVMはstoring only one copy of each literal String in the poolによって割り当てられるメモリの量を最適化できます。 このプロセスはinterningと呼ばれます。

String変数を作成してそれに値を割り当てると、JVMはプールで等しい値のStringを検索します。

見つかった場合、Javaコンパイラは、追加のメモリを割り当てることなく、メモリアドレスへの参照を返すだけです。

見つからない場合は、プールに追加され(インターン)、その参照が返されます。

これを確認するための小さなテストを書いてみましょう。

String constantString1 = "example";
String constantString2 = "example";

assertThat(constantString1)
  .isSameAs(constantString2);

3. コンストラクターを使用して割り当てられたStrings

new演算子を使用してStringを作成すると、Javaコンパイラは新しいオブジェクトを作成し、JVM用に予約されたヒープスペースに格納します。

このように作成されたすべてのStringは、独自のアドレスを持つ異なるメモリ領域を指します。

これが前のケースとどう違うか見てみましょう:

String constantString = "example";
String newString = new String("example");

assertThat(constantString).isNotSameAs(newString);

4. StringリテラルとString Object

When we create a String object using the new() operator, it always creates a new object in heap memory. On the other hand, if we create an object using String literal syntax e.g. “example”, it may return an existing object from the String pool, if it already exists.それ以外の場合は、新しいStringオブジェクトを作成し、将来の再利用のために文字列プールに配置します。

大まかに言うと、どちらもStringオブジェクトですが、主な違いは、new()演算子が常に新しいStringオブジェクトを作成するという点にあります。 また、リテラルを使用してStringを作成すると、インターンされます。

これは、Stringリテラルとnew演算子を使用して作成された2つのStringオブジェクトを比較するとはるかに明確になります。

String first = "example";
String second = "example";
System.out.println(first == second); // True

この例では、Stringオブジェクトは同じ参照を持ちます。

次に、newを使用して2つの異なるオブジェクトを作成し、それらが異なる参照を持っていることを確認しましょう。

String third = new String("example");
String fourth = new String("example");
System.out.println(third == fourth); // False

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

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

一般に、we should use the String literal notation when possible。 読みやすく、コンパイラーにコードを最適化する機会を与えます。

5. 手動インターン

インターンするオブジェクトでintern()メソッドを呼び出すことにより、Java文字列プールでStringを手動でインターンできます。

Stringを手動でインターンすると、その参照がプールに格納され、JVMは必要に応じてこの参照を返します。

このためのテストケースを作成しましょう:

String constantString = "interned example";
String newString = new String("interned example");

assertThat(constantString).isNotSameAs(newString);

String internedString = newString.intern();

assertThat(constantString)
  .isSameAs(internedString);

6. ガベージコレクション

Java 7より前は、JVMplaced the Java String Pool in the PermGen space, which has a fixed size — it can’t be expanded at runtime and is not eligible for garbage collection

Heapではなく)PermGenStringsをインターンするリスクは、Stringsが多すぎると、JVMからwe can get an OutOfMemory errorがインターンされることです。

Java 7以降、Java文字列プールはJVM.によってstored in the Heap space, which is garbage collectedになります。このアプローチの利点は、参照されていないStringsがプールから削除されるため、reduced risk of OutOfMemory errorになります。メモリを解放します。

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

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

-XX:MaxPermSize=1G

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

-XX:+PrintFlagsFinal
-XX:+PrintStringTableStatistics

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

-XX:StringTableSize=4901

Java 7u40より前は、デフォルトのプールサイズは1009バケットでしたが、この値は、最近のJavaバージョンでいくつかの変更の対象となりました。 正確には、Java 7u40からJava 11までのデフォルトのプールサイズは60013でしたが、現在では65536に増加しました。

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

8. Java9に関する注記

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

Java 9では、Compact Strings.と呼ばれる新しい表現が提供されます。この新しい形式は、保存されているコンテンツに応じて、char[]byte[]の間で適切なエンコーディングを選択します。

新しいString表現は必要な場合にのみUTF-16エンコーディングを使用するため、heapメモリの量は大幅に少なくなり、その結果、Garbage Collectorのオーバーヘッドが少なくなります。 (t4)s

9. 結論

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

この記事で使用されているすべてのコードサンプルは、over on GitHubで利用できます。