blog

JVMシリーズ:String.internとstringTable

すべての答えはこの記事にあるので、チェックしてみてください。 InternはStringクラスのネイティブメソッドなので、一番下にc++で実装されています。興味のある学生は、JVMのソースコードをチェ...

Sep 22, 2020 · 5 min. read
シェア

はじめに

StringTableとは何ですか?String.intern との関係は?StringTable は文字列オブジェクトの作成においてどのような役割を果たしますか?

すべての答えはこの記事の中にあります。

intern

internはStringクラスのネイティブ・メソッドなので、一番下にc++で実装されています。興味のある学生は、JVMのソースコードをチェックし、詳細を学ぶことができます。

ここでのポイントは、インターンの役割です。

インターンはこの文字列で表されるオブジェクトを返します。

intern メソッドが呼び出されたとき、StringTable に同じ String オブジェクトがすでに含まれている場合は、StringTable に格納されている String が直接返されます。

StringTable に同じオブジェクトがない場合は、この String オブジェクトが StringTable に追加され、この String オブジェクトへの参照が返されます。

つまり、s.intern() == t.intern()となるのは、s.equals(t)の場合だけです。

インターンと文字列定数

クラス・ファイルがクラス・ファイルにコンパイルされるとき、各クラス・ファイルには定数のプールがあることをご存知でしょうか。

文字列定数、クラス名とインターフェイス名、フィールド名、およびクラス内で参照されるその他の定数。

非常に単純なJavaクラスを見てください:

public class SimpleString {
 public String site="www.flydean.com";
}

そして、コンパイルされたクラスファイルの定数プールを見てください:

Constant pool:
 #1 = Methodref #2.#3 // java/lang/Object."<init>":()V
 #2 = Class #4 // java/lang/Object
 #3 = NameAndType #5:#6 // "<init>":()V
 #4 = Utf8 java/lang/Object
 #5 = Utf8 <init>
 #6 = Utf8 ()V
 #7 = String #8 // www.flydean.com
 #8 = Utf8 www.flydean.com
 #9 = Fieldref #10.#11 // com/flydean/SimpleString.site:Ljava/lang/String;
 #10 = Class #12 // com/flydean/SimpleString
 #11 = NameAndType #13:#14 // site:Ljava/lang/String;
 #12 = Utf8 com/flydean/SimpleString
 #13 = Utf8 site
 #14 = Utf8 Ljava/lang/String;
 #15 = Utf8 Code
 #16 = Utf8 LineNumberTable
 #17 = Utf8 LocalVariableTable
 #18 = Utf8 this
 #19 = Utf8 Lcom/flydean/SimpleString;
 #20 = Utf8 SourceFile
 #21 = Utf8 SimpleString.java

上記の結果では、クラス定数プールのインデックス7に文字列が格納され、この文字列の実際のコンテンツはUtf8エンコーディングのバリアントであるインデックス8に格納されていることがわかります。

 #7 = String #8 // www.flydean.com
 #8 = Utf8 www.flydean.com

さて、今問題が来て、実行時に定数プール内のクラスファイルは、JVMが実行時定数プールを識別できるように変換する必要があり、この実行時定数プールとStringTableとインターンはそれと何か関係がありますか?

javaオブジェクトのインスタンス化時に、すべての文字列リテラルは自動的にinternメソッドを呼び出します。

これが最初の呼び出しの場合、新しい String オブジェクトが作成され、String テーブルに格納され、その String オブジェクトへの参照が返されます。

intern が返す String オブジェクトの解析

上記の図から、また、文字列テーブルのStringオブジェクトに格納されて出てくることができる、それと通常のStringオブジェクトに違いはありませんが、また、オブジェクトのヘッダ、基礎となるバイト配列の参照、intハッシュ値などに分かれています。

私の言うことが信じられないなら、JOLを使って分析してください:

log.info("{}", ClassLayout.parseInstance("www.flydean.com".intern()).toPrintable());

出力を見てください:

INFO com.flydean.StringInternJOL - java.lang.String object internals:
 OFFSET SIZE TYPE DESCRIPTION VALUE
 0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
 8 4 (object header) 77 1a 06 00 (01110111 00011010 00000110 00000000) (399991)
 12 4 byte[] String.value [119, 119, 119, 46, 102, 108, 121, 100, 101, 97, 110, 46, 99, 111, 109]
 16 4 int String.hash 0
 20 1 byte String.coder 0
 21 1 boolean String.hashIsZero false
 22 2 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 2 bytes external = 2 bytes total

実際の問題の分析

以上の知識をもとに、次のような現実的な問題を分析してみましょう:

 String a =new String(new char[]{'a','b','c'});
 String b = a.intern();
 System.out.println(a == b);
 String x =new String("def");
 String y = x.intern();
 System.out.println(x == y);

とても簡単な2つの例の答えは? 答えは「真」と「偽」です。

最初の例は上記の原則に従ったもので、理解しやすいものです。 String aをビルドするとき、Stringテーブルには文字列 "abc "のインスタンスはありません。そこで intern メソッドは String テーブルにオブジェクトを追加し、そのオブジェクトへの参照を返します。

つまり、aとbは実際には1つのオブジェクトであり、trueを返します。

つ目の例はどうでしょう?Stringを初期化するとき、"def "という文字列もないのでは?なぜfalseを返すのでしょうか?

上のサブセクションで分析したことを覚えていますか?すべての文字列リテラルは、初期化時にデフォルトでinternメソッドを呼び出します。

つまり、"def "が初期化された時点で、すでに一度internを呼び出しており、この時点でStringテーブルにはすでにString "def "が存在しています。

つまり、xとyは2つの異なるオブジェクトであり、falseを返します。

上記の例はJDK7+以降で実行されているため、JDK6で実行している場合は誤った結果が得られることに注意してください。

JDK6とJDK7の違いは何ですか?

JDK6 では、StringTable はパーマネント世代に置かれるメソッド領域に格納されます。intern メソッドが呼び出されるたびに、String オブジェクトが String Table に存在しない場合、String オブジェクトのコピーが作成され、コピーされた String オブジェクトへの参照が返されます。

コピーが作成されたため、参照先は同じオブジェクトではなくなりました。結果は偽です。

JDK7後、StringTableは、Javaヒープに転送されているインターンメソッドを呼び出すと、StringTableは、直接StringオブジェクトStringTableに追加することができますので、同じオブジェクトを指します。

G1の重複排除関数は

Stringのコピーが頻繁に行われる場合、実は非常にメモリ容量を消費します。そこで、G1ゴミコレクタでは、以下のようにすればよいのです:

-XX:+UseStringDeduplication

をクリックして、文字列の重複排除をオンにします。

Stringオブジェクトの基本構造を思い出してください。それはバイト[]配列で、Stringの強調解除の原則は、バイト配列の下にある複数の文字列オブジェクトが同じ場所を指すようにすることです。こうしてメモリを節約します。

これは

-XX:+PrintStringTableStatistics

パラメータを渡すと、StringTable のサイズを確認できます。そして渡します:

-XX:StringTableSizen=n

で StringTable のサイズを指定します。

まとめ

この記事では、String.internとStringテーブルの関係について説明しています!

この記事を書いた人: Flydean procedure those things

この記事へのリンク

出典:フライディーンブログ

Read next

J42 プロトタイプチェーン

1.これを見つけるための普通の関数2.すべての配列はArrayクラスのインスタンスです1.すべてのオブジェクトはObjectクラスのインスタンスです2.すべての数値はNumberクラスのインスタンスです3.プロトタイプチェーン1.すべての関数は、属性:プロトタイプ(prototype)を持って生まれています。

Sep 22, 2020 · 2 min read