第二章Javaメモリ領域とメモリオーバーフロー異常(1)

4209 ワード

JAVAでよく見かけるいくつかの定数プールの違い
1.Classファイル定数プール
Classファイルにはクラスのバージョン情報、フィールド、メソッド、インタフェースなどの記述情報のほかに、Classファイル定数プールというものもあります.この定数プールはClassファイルのリソースウェアハウスとして理解できます.その中には主に2つの定数が格納されています.字面量と記号参照です.
字面量:テキスト文字列、「aaa」;finalタイプとして宣言された定数値などの記号参照:定数1)クラスとインタフェースの全限定名2)フィールドの名前と記述子3)メソッドの名前と記述子の3つに分類される
2.実行時定数プール
ランタイムプールはメソッド領域の一部であり、Classファイル定数プールの内容はコンパイル時に生成され、クラスロード後にランタイムプールに存在し、また、シンボル参照から変換された直接参照もランタイムプールに存在する.実行時定数プールのClassファイル定数プールに対する重要な特徴の1つは、動的性を備えていることです.つまり、定数はコンパイル時に発生するとは限らず、実行時に新しい定数を定数プールに入れることもできます.
3.文字列プール
これは難しい概念で、仕事の中で、Stringクラスは私たちが使用する頻度が非常に高いオブジェクトタイプです.JVMは、パフォーマンスを向上させ、メモリのオーバーヘッドを削減するために、文字列の重複作成を回避し、特殊なメモリ領域を維持します.つまりString Poolです.このメモリの部分は以前はメソッド領域で、jdk 1.8以降はメソッド領域が削除されていましたが、Metaspace領域に代わって、この文字列プールはこのMetaspaceに分類されているのではないでしょうか(疑問ですが、まだ分かりません).
Javaでは文字列オブジェクトを作成する方法が2つあることを知っています.1)文字面値で値を割り当てる2)newキーワードで文字列オブジェクトを新規作成します.この2つの方法は、パフォーマンスとメモリの消費量に差があります.
方式一:字面値を用いて値を付与する.例えば:
String a = "aaa";
String b = "aaa";
System.out.println(a == b)

JVMはまず文字列プールに「aaa」というオブジェクトが存在するかどうかを検索し、存在しない場合は文字列プールに「aaa」というオブジェクトを作成し、プールの「aaa」というオブジェクトの参照アドレスを文字列定数aに返します.このaはプールの「aaa」という文字列オブジェクトを指します.存在する場合、オブジェクトは作成されず、プール内の「aaa」というオブジェクトのアドレスを直接返し、文字列定数bに割り当てます.したがって、a=bの戻り値はtrueである、両方が文字列プールの「aaa」を指すためである.
方法2:newキーワードを使用して文字列オブジェクトを新規作成します.たとえば、次のようにします.
String a = new String("aaa");
String b = new String("aaa");
System.out.println(a == b);

newキーを使用すると、JVMはまず定数プールから「aaa」文字列の有無を表示し、ある場合は新しいnewから出てきたスタックメモリにコピーし、スタックメモリのアドレスを返します.もしなかったら、直接スタックの中でnewが出てきて「aaa」の値を格納し、同じようにスタックメモリのアドレスを返すと、問題が発生します.
このとき、このスタックメモリの「aaa」も文字列プールに作成されますか?
この問題はネット上で議論が大きく、この時も文字列プールに1部作成するという説はあまり認められません.そうすると、スタックと文字列プールの完全な重複をもたらすのではないでしょうか.すなわち、文字列プールに「aaa」があるかどうかにかかわらず、私がnewであれば、スタックと文字列プールに「aaa」が同時に存在する.これでメモリの無駄になるのではないでしょうか.もう1つの言い方は、文字列プールに「aaa」がない場合は、まずスタックに「aaa」を作成し、文字列プールに「aaa」を追加する必要がある場合は、Stringのinternメソッドを呼び出すということです.個人的にはこの説を認めています.
Internメソッドについて
internメソッドでは、クラスStringによって独自に維持される最初は空の文字列プールが使用されます.Internメソッドが呼び出されると、プールにこのStringオブジェクトに等しい文字列がすでに含まれている場合(equals(oject)メソッドで決定される)、プール内の文字列が返されます.そうでなければ、このStringオブジェクトをプールに追加し、このStringオブジェクトの参照を返します.任意の2つの文字列sおよびtについて、s.equals(t)がtrueである場合にのみ、s.instan()==t.instanがtrueとなる.すべてのフォント値文字列と文字列付与定数式はinternメソッドを使用して操作されます.
よくある例を見てみましょう
String s1 = "Hello";
String s2 = "Hello";
String s3 = "Hel" + "lo";
String s4 = "Hel" + new String("lo");
String s5 = new String("Hello");
String s6 = s5.intern();
String s7 = "H";
String s8 = "ello";
String s9 = s7 + s8;
           
System.out.println(s1 == s2);  // true
System.out.println(s1 == s3);  // true
System.out.println(s1 == s4);  // false
System.out.println(s1 == s9);  // false
System.out.println(s4 == s5);  // false
System.out.println(s1 == s6);  // true

まず、javaでは==オペレータを直接使用し、2文字列の参照アドレスを比較します.比較内容ではありません.比較内容はStringを使用してください.equals().
s 1==s 2これは非常によく理解されており、s 1、s 2は値を付与する際に、いずれも文字列の字面量を使用し、白話点を言うと、文字列を直接書き込み、コンパイル中にclassファイルの定数プールに直接入れることで多重化を実現し、実行時定数プールにロードした後、s 1、s 2は同じメモリアドレスを指すので等しい.
s 1==s 3この場所には穴があり、s 3は動的につなぎ合わせられた文字列であるが、すべてのつなぎ合わせに参加する部分は既知の字面量であり、コンパイル期間中、このようなつなぎ合わせは最適化され、コンパイラは直接つなぎ合わせてくれるので、String s 3=「Hel」+「lo」である.classファイルでString s 3="Hello"に最適化されます.だからs 1==s 3が成立します.
s 1==s 4はもちろん等しくなく、s 4もつなぎ合わせられているが、new String(「lo」)という部分は既知の字面量ではなく、予想できない部分であり、コンパイラは最適化されず、実行時になってから結果を確定しなければならない.
s 1==s 9も等しくなく、理屈の差は多くなく、s 7、s 8が値を付与する際に使用する文字列の字面量であるが、s 9に接合する場合、s 7、s 8は2つの変数として予想できない.メソッド領域定数プールのs 1アドレスと同じではありません.
s 4==s 5はもう説明する必要はありません.絶対に等しくありません.両者はスタックの中にありますが、住所は違います.
s 1==s 6の2つの等しいのはinternメソッドのおかげで、s 5はスタックの中で、内容はHelloで、internメソッドはHello文字列を定数プールに追加しようとし、定数プールの中のアドレスを返します.定数プールにはすでにHello文字列があるので、internメソッドは戻りアドレスを直接接続します.一方、s 1はコンパイル期間中に定数プールを指しているため、s 1とs 6は同じアドレスを指し、等しい.
これは読書ノートにすぎません.多くの内容は他の先輩からの投稿と「Java仮想マシンを深く理解する」という本で、すべてのソースがリストされています.
Java文字列プールと文字列スタックのメモリ割り当てStringは、実行時定数プールに入れるタイミングとStringである.intern()メソッドJava 6,7,8のStringを解く.intern–文字列プールJavaの文字列定数プールとJavaのスタックとスタックの違いJava文字列プール(String Pool)深さ解析タッチjava定数プールJavaのいくつかの定数プールの区別