Strings = newString( "xyz")はいくつかのインスタンスを作成します

面接の質問から話す

String s = new String("xyz"); 创建了几个实例?

これは古典的なインタビューの質問です。いわゆるJavaの本で、私が見た「標準的な答え」は次のとおりです。

两个,一个堆区的“xyz”,一个栈区指向“xyz”的s。

このいわゆる「標準的な答え」にはスロットが多すぎるので、後でゆっくり分析します。

答えはとんでもないですが、質問は「作成」の具体的な意味と「作成」の時間を指定するものではないので、質問自体には意味がないと思います。ランタイムですか?パッケージにクラスの読み込みが含まれていない場合は?コンテキストコードコンテキストはありますか?インスタンスが何を参照するかについての定義もありません、それはJavaインスタンスですか?または、文字列インスタンスを参照しますか?パッケージにはJVMにC ++インスタンスが含まれていませんか?

明らかに、この問題は「問題のある問題」です。この答えも「疑わしい答え」です。

文字列構造

分析の前に、メモリグラフの後続の描画を容易にするために、Javaの文字列構造の一般的な理解が必要です。

Strings = newString( "xyz")はいくつかのインスタンスを作成します

上の図からわかるように、Stringクラスには次の3つの属性があります。

  • 値:文字を格納するために使用されるchar配列。
  • hash:キャッシュされた文字列のハッシュコード。デフォルトは0です(文字列のハッシュ値は、hashCodeメソッドが実際に呼び出されたときに計算されます)。
  • serialVersionUID:シリアル化に使用されます。

通常の問題と合理的な説明

上記の語幹にいくつかの修飾子を追加して、新しい質問を取得します。

String s = new String("xyz");创建几个String实例?

この質問について、いくつかの高く評価された答えがインターネットで見つけることができます:

两个。
一个是字符串字面量"xyz"所对应的、存在于全局共享的常量池中的实例,
另一个是通过new String(String)创建并初始化的、内容(字符)与"xyz"相同的实例。
考虑到如果常量池中如果有这个字符串,就只会创建一个。同时在栈区还会有一个对new出来的String实例的s。

スタックとヒープを考慮し、定数プールについて言及すると、これはこの質問への回答に対するほとんどのインタビュアーの期待に達していると思います。おそらく、これはインタビュアーが調査したいポイントでもあります。

しかし、この答えは合理的であり、完全に正しいわけではありません。

まず、多くの回答者が常に「文字列定数プール」ではなく「定数プール」を使用している理由がわかりません。Javaシステムには、実際には3つの定数プールがあります。3つの定数プールの概念と有用性は次のとおりです。同じではなく、一緒に混合すると、他の人に誤解を招きやすくなります。

第二に、回答者が言及した「定数プール」が「文字列定数プール」であっても、「文字列定数プール」は文字列ではなく文字列インスタンスへの参照を格納するため、大きな違いがあります。そして、この答えは、コードが実行される環境を考慮していません。

これらの問題は、以下で1つずつ分析されます。

変数とインスタンスを区別する

最初の質問と「標準的な答え」に戻りましょう。

问题:String s = new String("xyz"); 创建了几个实例?
答案:两个,一个堆区的“xyz”,一个栈区指向“xyz”的s

明らかに、答えを書いた人は変数と例を区別しませんでした。Javaでは、変数は変数であり、型の変数はオブジェクトインスタンスまたはnull専用であり、インスタンス自体ではありません。宣言された変数の数は、必ずしも作成されたインスタンスの数に関連しているわけではありません。

例えば:

String s1 = "xyz";  
String s2 = s1.concat("");  
String s3 = null;  
new String(s1);  

このコードには、次の3つの文字列型変数が含まれます。

  • s1は、以下のStringインスタンスの1を指します
  • s2、s1と同じポイント
  • s3、値はnullであり、インスタンスを指していません

そして3つの文字列インスタンス:

  • 「xyz」リテラルに対応する常駐文字列定数の文字列インスタンス
  • ""リテラルに対応する常駐文字列定数のStringインスタンス
  • new String(String)によって作成された新しいStringインスタンスで、変数はそれを指していません。

クラスの読み込み

String s = new String( "xyz");の場合、いくつのStringインスタンスが作成されますか?この問題。

インターネット上のすべての回答は、クラスの読み込みプロセスと実際の実行プロセスを一緒に分析しているようです。

特定のコードフラグメントを実行するには、そのコードフラグメントが含まれているクラスをロードする必要があり、同じクラスローダーの場合は最大で1回ロードされるため、問題はないようです。

しかし、このコードのバイトコードを見てみましょう:

Strings = newString( "xyz")はいくつかのインスタンスを作成します

新しいjava / lang / Stringは1回だけ表示されるようです。つまり、1つのStringインスタンスのみが作成されます。つまり、元の質問のコードは、実行されるたびに新しいStringインスタンスを作成するだけです。ここでのldc命令は、クラスのロードプロセスで作成されたStringインスタンス( "xyz")の参照をオペランドスタックの最上位にプッシュするだけで、新しいStringインスタンスは作成しません。

2つのインスタンスがあるべきではありませんか?別のStringインスタンスが作成されたのはいつですか?

クラスロードの解析フェーズは、Java仮想マシンが定数プール内のシンボリック参照を直接参照に置き換えるプロセスであることは誰もが知っています。JVM仕様によれば、準拠するJVM実装は、Stringインスタンスを作成して常駐させる必要があります。クラスロードプロセス中は一定。特にクラスロードの解析段階では、「xyz」リテラルに対応します。この定数はグローバルに共有され、同じ内容の文字列が以前に存在しなかった場合にのみ、新しい文字列インスタンスを作成する必要があります。

したがって、クラスの読み込みの解析段階では、Stringインスタンスが実際に作成されており、コードが実行されると、Stringインスタンスが新しく作成されていることがわかります。もちろん、この2つを組み合わせて議論しても問題ありません。

JVMの最適化

上記の説明は、仕様で定義されているJava言語とJava仮想マシンのみを対象としています。これは概念的には当てはまりますが、実際のJVM実装はより最適化できます。元の質問のコードフラグメントは、Stringインスタンスが実際に実行されたとき(スペースが割り当てられていないとき)に完全に作成されない場合があります。

コンテキストコードを考慮せずに「標準的な答え」であると言うことは不正です。

このコードを見てみましょう:

Strings = newString( "xyz")はいくつかのインスタンスを作成します

このコードを実行すると、メモリを消費するStringオブジェクトが引き続き作成され、GCが頻繁に発生します。

この結論については誰もが意見を持っていないと思います。-XX:+ PrintGC-XX:-DoEscapeAnalysisを追加してログを印刷し、エスケープ分析をオフにします(JDK8はデフォルトでこの最適化をオンにし、最初にオフにします)

Strings = newString( "xyz")はいくつかのインスタンスを作成します

実行して確認します。

Strings = newString( "xyz")はいくつかのインスタンスを作成します

結果は確かに私たちが期待したとおりであり、常にStringオブジェクトを作成し、メモリを消費して頻繁にGCを引き起こします。

ここで、-XX:-DoEscapeAnalysisを-XX:+ DoEscapeAnalysisに変更し、次のコードを再度実行します。

Strings = newString( "xyz")はいくつかのインスタンスを作成します

不思議なことが起こり、実行を続けたところ、GCログは出力されなくなりました。新しく作成されたStringオブジェクトはもうメモリを消費しませんか?

実際の状況は次のとおりです。HotSpotVMの最適化後、newString()メソッドは新しいStringインスタンスを作成しません。このようにして、メモリは自然に消費されず、GCはトリガーされなくなります。

最初の質問を見てみましょう。特定の状況を組み合わせることなく、String s = new String( "xyz");が2つのStringインスタンスを作成すると簡単に言うことができますか?

エスケープ分析の例を示しました。HotSpotVMには、メソッドのインライン化、スカラー置換、不要なコードの削除など、このような多くの最適化があります。

クラスオープン

トピックに「Java」インスタンスの属性が追加されていない場合は、JVMのoopインスタンスを無視しないでください。

これを後でよりよく説明するために、klass-oppモデルの知識を追加する必要があります。JVMの特定の実装に関連するコンテンツが、Jdk8のHotSpot VMに基づいている限り、最初に合意します。

HotSpotVMはC ++に基づいて実装され、C ++はオブジェクト指向言語であり、それ自体がオブジェクト指向の基本的な特性を備えているため、Javaでオブジェクトを表す最も簡単な方法は、各Javaクラスに対応するC ++クラスを生成することです。しかし、HotSpot VMはこれを行いませんでしたが、一連のklass-oopモデルを設計しました。

klass、これはJVM内のJavaクラスのメタ情報の存在形式です。JavaクラスがJVMクラスローダーによってロードされた後、それはklassの形式でJVMに存在します。

Strings = newString( "xyz")はいくつかのインスタンスを作成します

おっと、それはJVM内のJavaオブジェクトの存在形式です。新しいオブジェクトが作成されるたびに、対応するタイプのOOPオブジェクトがJVMに作成されます。

その中で、instanceOopDescは非配列オブジェクトを表し、arrayOopDescは配列オブジェクトを表します。

また、objArrayOopDescは参照型の配列オブジェクトを表し、typeArrayOopDescは基本的な型の配列オブジェクトを表します。

例:JavaのStringクラスのインスタンスの場合、JVMには対応するinstanceOopDescインスタンスがあります。

Strings = newString( "xyz")はいくつかのインスタンスを作成します

文字列定数プール

Javaシステムには、次の3つの定数プールがあります。

  • クラスバイトコードの定数プール:ハードディスクに存在します。定数には、主にリテラルとシンボリック参照の2種類があります。
  • ランタイム定数プール:メソッド領域の一部。私たちがよく言う定数プールは、この領域、つまりメソッド領域の実行時定数プールを指します。
  • 文字列定数プール:ヒープ領域に存在します。この定数プールは、JVMレベルのStringTableであり、java.lang.Stringインスタンスへの参照のみを格納し、Stringオブジェクトの内容は格納しません。一般に、文字列が文字列定数プールに入るとは、その文字列への参照がこのStringTableに保存されることを意味します。逆に、文字列が文字列にない場合は、StringTableにその文字列への参照がないことを意味します。 。

今日、私たちが理解したいのは文字列定数プールです。

文字列定数プール、つまり文字列プール。JVMの対応するクラスはStringTableであり、基礎となる実装はHashtableです。ハッシュのアイデアが使用されます。

Strings = newString( "xyz")はいくつかのインスタンスを作成します

次のコードは、文字列定数プールに文字列メソッドを追加することです。これはC ++コードですが、Javaを学んだことのある人なら誰でも理解できるか、少なくともこのコードの機能を理解できると思います。添え字インデックスは、文字列の内容+長さによって生成されたハッシュ値によって特定され、Java Stringクラスのインスタンスに対応するinstanceOopDescは、ストレージ構造としてHashtableEntryにカプセル化され、定数プールに格納されます。

Strings = newString( "xyz")はいくつかのインスタンスを作成します

文字列定数プールの知識を補足した後、記事の冒頭の質問に戻ります。

String s = new String( "xyz");作成されたインスタンスの数は?

メモリ図を描画します。この図では、Stringに対応するinstanceOopDescの2つのインスタンスが省略されています。

Strings = newString( "xyz")はいくつかのインスタンスを作成します

答えを得るのは難しくありません:

如果包括JVM中的C++实例的话,
有两个Java的String实例,
两个String实例对应的instanceOopDesc实例,
还有一个char[]数组对应的typeArrayOopDesc实例。
加一起一共是5个,也可以说2个String实例加上3个oop实例。

総括する

String s = new String( "xyz");作成されたインスタンスの数は?

上記の分析を通じて、このトピックの語幹に形容詞が追加されるたびに、トピックの回答が異なることがわかります。

クラスのロードプロセスを検討するかどうか、JVMの最適化を検討するかどうか、対応するoopインスタンスを含めるかどうかなど、各ポイントについて説明する価値があります。

次回誰かがあなたに尋ねたとき、あなたはこの記事を彼と共有したほうがよいでしょう。

おすすめ

転載: blog.csdn.net/doubututou/article/details/111865890