インタビューの質問シリーズパート6:JVM文字列定数プールと文字列インターンメソッドの詳細?

「Java Interview Question Series」:長い知識と非常に興味深いコラム。掘り下げ、ソースコードの分析、原則の要約、写真とテキストの組み合わせ、公式アカウントでの一連の記事の作成、インタビューのレベルの引き上げなど。[Program New Vision]に引き続きご注目ください。これは第6章です。

文字列の比較については、前回の記事で詳しく説明しましたが、この記事では、文字列定数プールの格納と、インターン方式を使用した場合のメモリの変化に基づいて、手順を追って説明します。

キーコンテンツ:文字列がインターンメソッドを呼び出してから文字列を比較すると、どのような変更が行われますか?

この記事の内容は、HotSpot仮想マシンに基づいて説明されています。

面接の質問

最初に、インタビューの質問画像を通じて、この記事で説明するコンテンツのプレゼンテーション形式を理解しましょう。

String s1 = new String("he") + new String("llo");
String s2 = new String("h") + new String("ello");

String s3 = s1.intern();
String s4 = s2.intern();
System.out.println(s1 == s3);
System.out.println(s1 == s4);

上記のコードを実行すると、出力結果がすべて正しいことがわかります。それでは、インターンメソッドを呼び出した後、等しくない文字列が等しくなるのはなぜですか?基礎となる実装を段階的に分析しましょう。

インターン方式の役割

intern()メソッドの関数定義:

(1)現在の文字列の内容が文字列定数プールに存在する(つまり、equals()メソッドがtrueである、つまり内容が同じである)場合、定数プール内のこの文字列の参照を直接返します。

(2)現在の文字列が文字列定数プールにない場合は、定数プールに参照を作成し、ヒープにすでに存在する文字列をポイントしてから、定数プールに参照を返します。

簡単に言うと、インターンメソッドは、文字列が文字列定数プールに存在するかどうかを判断し、存在しない場合は作成し、存在する場合はそれを返します。

文字列定数プール

HotSpotで文字列定数プール関数を実装しているのはStringTableクラスです。これは、デフォルト値が1009のハッシュテーブルです。HotSpot仮想マシンの各インスタンスにはコピーが1つだけあり、すべてのクラスで共有されます。文字列定数は文字で構成され、StringTableに配置されます。

インタビューの質問シリーズ第5番:JDKランタイム定数プール、文字列定数プール、静的定数プール、まだ愚かで混乱していますか?「この記事では、JDKバージョンでの文字列定数プールの変更の場所を具体的に紹介しました。参照できます。

JDK6以前のバージョンでは、文字列定数プールはPerm Gen領域(メソッド領域)に配置されます。StringTableの長さは固定されており、長さは1009です。String文字列が多すぎると、ハッシュの競合が発生し、リンクリストが非常に長くなり、パフォーマンスが大幅に低下します。このとき、すべての文字列定数(リテラル値)は文字列定数プールに配置されます。

永続的な世代の制限された固定されたスペースのため、JDK6のストレージモードはOutOfMemoryErrorを簡単に引き起こす可能性があります。

そして、JDK7は永続的な世代に取り組んでいたため、文字列定数プールがヒープに置かれました。このとき、ヒープのサイズが固定されていても、アプリケーションの調整は、ヒープのサイズのみを調整すればよい。

JDK7では、文字列定数プールは文字列定数を格納できるだけでなく、文字列参照も格納できます。つまり、ヒープ内の文字列への参照は、定数プールの値として存在できます。

文字列プーリングプロセス分析

上記の基本理論を理解した後、写真とテキストを組み合わせた形で、文字列プーリングのプロセスと分類を段階的に示します。以下の例は、JDK8バージョンに基づいて分析および説明されています。

文字列を二重引用符で宣言すると、次のようになります。

String wechat = "程序新视界";

このとき、二重引用符で囲まれた文字列は、文字列定数プールに直接格納されます。
ここに画像の説明を挿入

上記のストレージ構造については、前の記事ですでに触れたので、あまり説明しません。次に、同じ文字列をもう一度宣言して、どのような変更が行われるかを確認します。

String wechat = "程序新视界";
String wechat1 = "程序新视界";

上記のコードでwechat1が宣言されている場合、対応する文字列は定数プールに既に存在していることがわかり、再作成されませんが、対応する参照がwechat1に返されます。対応する構造図は次のとおりです。
ここに画像の説明を挿入

この時点で、wechatとwechat1を二重の等号と直接比較すると、それらの参照とリテラル値が同じであるため、それらは完全に等しくなります。

上記は直接二重引用符の割り当ての場合ですが、新しい形式の文字列を作成するプロセスは何ですか?前回の記事では、これら2つの状況について説明しました。定数プールには対応する値があり、対応する値はありません。

String wechat2 = new String("程序新视界");

対応する値がある場合、wechat2変数へのオブジェクト参照が最初にヒープに作成され、次にオブジェクト参照は文字列定数プールに既に存在する定数を指します。
ここに画像の説明を挿入

この時点で、wechat変数とwechat2変数を比較するための二重等号の直接使用は絶対に等しくありませんが、equalsメソッドによる比較のリテラル値は等しいです。

別の状況は、newによって作成されたときに、文字列定数プールに対応する定数がないことです。この場合、文字列定数は文字列定数プールに作成され、次に定数プール内の対応する文字列への参照を保持する文字列がヒープに作成されます。そして、ヒープ内のオブジェクトのアドレスをwechat2に返します。最終的なレンダリングは、上記のとおりです。

このとき、新しい文字列を直接割り当てず、+記号で操作すると、状況が異なります。

String s1 = "程序";
String wechat3 = new String(s1 + "新视界");

上記のコードs1は定数プールに格納され、wechat3の値はヒープにStringオブジェクトを作成するだけです。JVMのコンパイル時にStringBuilderを使用してプラス記号を接合するため、対応する文字列は定数プールに格納されません。
ここに画像の説明を挿入

現時点での状況は、インタビューの質問に文字列を作成することをすでに含んでいます。それでは、インターンメソッドを使用してプーリング操作を実行し、文字列定数プールの特定の変更を確認しましょう。

上記のコードを例にとると、現時点では、wechat、wechat1、wechat2、wechat3の3つの変数は、二重の等号と比較すると明らかに等しくありません。次に、wechat3がインターンプールされます。

String s1 = "程序";
String wechat3 = new String(s1 + "新视界");
wechat3 = wechat3.intern();

wechatとwechat1の値がwechat3の値と等しいことがわかります。wechatとwechat1は実際には1つであるため、ここでは、このプロセスを分析する例として、wechatとwechat3の比較のみを取り上げます。

internメソッドが呼び出される前のメモリの状態は、次の図に示すとおりです(s1の部分は無視されます)。

画像

それらの値が上の図で等しくないのは当然のことです。次に、wechat3をプールし、プーリングの結果を上記のコードであるwechat3に割り当てます。メモリ構造は次のように変更されます。
ここに画像の説明を挿入

この時点で、参照とリテラル値がすべて同じであり、等しいため、対応する2つの値が判断されます。上記の特定のインターン判定ルールはすでに知っていますが、対応する値が定数プールに存在する場合、参照は直接返されます。

次に、別の状況があります。つまり、対応する値が定数プールにどのように存在しないのでしょうか。まず、次のコードを見てください。

String s2 = "关注";
String wechat4 = new String(s2 + "公众号");
wechat4 = wechat4.intern();

インターンが呼び出される前の操作。すでにStringオブジェクトがヒープに作成され、wechat3のグラフのようにコピーが定数プールに格納されないことは既に述べました。

現時点では、定数プールには対応する文字列がありません。このとき、インターンメソッドを呼び出した後のメモリ構造は次のとおりです。
ここに画像の説明を挿入

インターンメソッドの後、ヒープ内の対応する文字列への参照が定数プールに格納されます。上記とは対照的に、JDK7以降の文字列定数プールは参照を格納できます。

なお、対応する文字列が文字列定数プールに存在しない場合、インターンメソッドの呼び出しで返されるアドレスは、図中の0x99に対応するヒープ内のアドレスとなります。wechat4の元のアドレスはヒープ内のアドレスを指しているため、変更されません。

この時点で、二重引用符で割り当てられたwechat5を定義する場合、次のコード:

String s2 = "关注";
String wechat4 = new String(s2 + "公众号");
wechat4 = wechat4.intern();

String wechat5 = "关注公众号";
System.out.println(wechat4 == wechat5);

変数wechat5が初期化されると、文字列定数プールに参照があることがわかり、wechat5はこの参照を直接指します。つまり、wechat5とwechat4はどちらもメモリ内のStringオブジェクトを指します。
画像

概要

上記のデモで注意すべき重要な点は、インターンメソッドによって返される参照アドレスです。対応する文字列が文字列定数プールに既に存在する場合、この時点で文字列定数のアドレスが返されます(文字列は定数プールに格納されます)。対応する文字列が文字列定数プールに存在しない場合は、ヒープ内の参照は定数プールの対応する位置に配置されます(定数プールは文字列の参照をヒープに格納します)。このとき、インターンはヒープ内の文字列に対応する参照を返します。

上記の戻りロジックを明確にした後、元のコードを見てください。

String s1 = new String("he") + new String("llo");
String s2 = new String("h") + new String("ello");

String s3 = s1.intern();
String s4 = s2.intern();
System.out.println(s1 == s3);
System.out.println(s1 == s4);

ここで、s1はヒープ内の文字列「hello」のアドレスであり、s2はヒープ内の別の文字列「hello」のアドレスです。s1.intern()の場合、s1のアドレスは定数プールに格納されます。このとき、s1.intern()はs1のアドレスを返すため、s1 = s3と同じアドレスになります。

次に、s2.intern()を実行します。この時点では、定数プールにはhello文字列が既に存在します。タイプは参照であり、s1のアドレスを指します。実行後、s1のアドレスが返され、s4に割り当てられるため、s1とs4も同じを指します。したがって、アドレスは同じです。

上記のより深い分析を通じて、誰もが文字列定数、文字列定数プール、およびインターンメソッドをより深く理解する必要があります。このように関連するインタビューの質問を分析すると、基本的には正確に回答することができます。

元のリンク:「インタビューの質問シリーズパート6:JVM文字列定数プールと文字列インターンメソッドの詳細?

参照記事:

https : //www.zhihu.com/question/55994121


手続きの新しいビジョン

パブリック アカウント New Vision of Program」は、ソフトパワーとハードテクノロジーを同時に改善し、大量のデータを提供できるプラットフォームです。

WeChat公式アカウント:プログラムの新しいビジョン

おすすめ

転載: blog.csdn.net/wo541075754/article/details/108374856
おすすめ