JVM: JVM メモリのパーティショニング

概要
仕事や仕事で C または C++ を学習したことのある読者は、これら 2 つの言語のメモリ管理メカニズムが Java のメモリ管理メカニズムとは異なることにきっと気づくでしょう。C または C++ でプログラミングする場合、プログラマはメモリを手動で管理および維持する必要があります。つまり、不要なオブジェクトを手動でクリアする必要があります。そうしないと、メモリ リークやメモリ オーバーフローの問題が発生します。

Java 言語を使用して開発する場合、ほとんどの場合、不要なオブジェクトのリサイクルやメモリ管理について気にする必要がないことがわかります。これは、JVM 仮想マシンがすでにそれを行ってくれるためです。JVM メモリのさまざまな領域を理解することは、JVM メモリの管理メカニズムを洞察し、メモリ関連の問題を回避し、効率的に解決するのに役立ちます。

問題の原因
Java でプログラミングするとき、一時変数、静的変数、オブジェクト、メソッド、クラスなど、さまざまな種類のデータを使用します。では、保管方法に違いはあるのでしょうか?それともどこに存在するのでしょうか?

実行時データ領域
Java プログラムの実行中、Java 仮想マシンは、管理するメモリを、それぞれ独自の目的を持ついくつかの異なるデータ領域に分割します。
ここに画像の説明を挿入

このうち、ヒープとメソッド領域はスレッド間で共有されますが、スタックとプログラム カウンタはスレッドプライベートです。

プログラム カウンタは
スレッドプライベートであり、現在のスレッドによって実行されたバイトコードの行番号のインジケータとして見ることができます。バイトコード インタプリタが動作すると、このカウンタの値を変更して、次に実行するバイトコード命令を選択します。分岐、ループ、例外処理、スレッド回復などの基本的な機能はすべて、このカウンターに依存して完了する必要があります。
これは、OOM 例外が指定されていない唯一の領域です。
仮想マシン スタック
仮想マシン スタックもスレッドプライベートであり、スレッドと同じライフサイクルを持ちます。スタックには、メソッドのローカル変数、オブジェクトへの参照などが保存されます。
この領域では 2 つの例外が指定されており、スレッドが要求したスタックの深さが仮想マシンで許可されている深さよりも大きい場合、StackOverflowError 例外がスローされます。仮想マシン スタックの動的拡張が十分なメモリを適用できない場合、OOM 例外がスローされます。
ネイティブ メソッド スタックは
、ネイティブ メソッドを提供することを除いて、仮想マシン スタックと同じ機能を持ちます。HotSpot 仮想マシンは、仮想マシン スタックとローカル メソッド スタックを 1 つに直接結合します。
ヒープ ヒープ
は、Java 仮想マシンによって管理される最大のメモリ部分です。これはすべてのスレッドによって共有され、仮想マシンの起動時に作成されるメモリ領域です。この領域の唯一の機能は、オブジェクト インスタンス、つまり NEW からのオブジェクトを格納することです。この領域は、Java ガベージ コレクターの主要な動作領域でもあります。
ヒープのサイズを拡張できなくなると、OOM 例外がスローされます。
このメモリ領域の唯一の機能はオブジェクト インスタンスを格納することであると言え、ほとんどすべてのオブジェクト インスタンスと配列はここにメモリを割り当てます。
Java ヒープはガベージ コレクションによって管理される主要な領域であるため、GC ヒープとも呼ばれます。ガベージ コレクションでは世代別ガベージ コレクション アルゴリズムが使用されるため、Java ヒープを新世代 (詳細は Eden、From Survivor、To Survivor) と旧世代に分割することもできます。さらに分割する目的は、メモリをより適切に再利用したり、メモリをより速く割り当てることです。
ここに画像の説明を挿入

メソッド領域
メソッド領域もスレッドが共有するメモリ領域で、仮想マシンがロードしたクラス情報、定数、静的変数などを格納するために使用されます。メソッド領域がメモリ割り当て要件を満たせない場合、OOM 例外がスローされます。この領域は永続世代としても知られています。
補足
上図には実行時定数プールとダイレクトメモリがありませんが、この2つの部分も開発中に頻繁に触ります。それで、みんなに追加してください。

実行時コンスタント プール 実行時
コンスタント プールはメソッド領域の一部であり、クラス ファイルにはクラスのバージョン、フィールド、メソッド、インターフェイスなどの記述情報に加えて、コンスタント プールと呼ばれる情報もあります。コンパイル中に生成されるさまざまな型を格納するために使用されます。リテラル参照およびシンボリック参照。この部分は、クラスがロードされた後、メソッド領域のランタイム定数プールに格納されます。OOM 例外もスローされます。
ダイレクト メモリ
ダイレクト メモリは、仮想マシンの実行時にデータ領域の一部ではなく、Java 仮想マシンの仕様で定義されているメモリ領域でもありませんが、NIO 操作中に直接使用されるメモリの一部です。は仮想マシンのパラメータによって制限されませんが、それでもマシンの合計メモリによって制限され、OOM 例外がスローされます。
ここで皆さんに理解していただきたい概念があります. ヒープ内で世代別ガベージ コレクション アルゴリズムを使用する場合の永続的な代表メソッド領域はヒープ メモリ内にはありません. 上の図は、世代別ガベージ コレクション アルゴリズムが機能することを示すためにそれをまとめたものですこれらの領域について。

JDK 1.8 での変更点
メソッド領域はスレッドで共有され、主にクラス情報、定数プール、メソッドデータ、メソッドコードなどを格納するために使用されます。この領域を永続世代と呼びます。これは、JVM ガベージ コレクションが関与する領域でもあります。

ほとんどのプログラマーは、java.lang.OutOfMemoryError: PermGen スペース例外を目にしたことがあるはずです。ここで、PermGen スペースは実際にはメソッド領域を参照しています。メソッド領域には主にクラスの関連情報が格納されるため、動的に生成されたクラスの場合、永続世代のメモリがオーバーフローする可能性が高くなります。典型的なシナリオは、多数の JSP ページがあり、メソッド領域のメモリ オーバーフローです。永続的な生成が発生しやすくなります。

JDK 1.8 では、HotSpot 仮想マシンには PermGen スペース メソッド領域がなくなり、Metaspace (メタスペース) と呼ばれるものに置き換えられました。
ここに画像の説明を挿入

メタスペースとメソッド領域の最大の違いは、メタスペースが仮想マシン内に存在せず、ローカル メモリを使用することです。デフォルトでは、メタスペースのサイズはローカル メモリによってのみ制限されます。

定数領域はもともとメソッド領域にありましたが、現在はメソッド領域が削除されているため、定数プールはヒープに配置されています。

これを行うことの利点は次のとおりです。

この変更の利点:

文字列定数はメソッド領域に格納されるため、パフォーマンス上の問題やメモリ オーバーフローが発生しやすくなります。
クラスやメソッド情報のサイズを決めるのが難しいため、メソッド領域のサイズを指定するのが難しく、小さすぎるとメソッド領域がオーバーフローし、大きすぎるとエラーが発生しやすくなります。ヒープ内のスペースが不十分です。
メソッド領域でのガベージ コレクションは不要な複雑さをもたらし、回復効率が低くなります (ガベージ コレクションについては次の章で紹介します)。
仮想マシン オブジェクトのわかりやすく説明
オブジェクト作成プロセスを覚えて、各ステップが何をしているのかを理解しておくことが最善です。
ここに画像の説明を挿入

クラス ロード チェック: 仮想マシンが新しい命令を検出すると、まずこの命令のパラメータが定数プール内でこのクラスのシンボルを見つけることができるかどうかをチェックし、このクラスのシンボル参照によって表されるクラスがロードされているかどうかをチェックします。解析され、初期化されました。そうでない場合は、対応するクラスのロード プロセスを最初に実行する必要があります。簡単に言えば、オブジェクトのクラスがロードされているかどうかによって決まります。
メモリの割り当て: クラス読み込みチェックに合格した後、仮想マシンは新しいオブジェクトにメモリを割り当てます。オブジェクトに必要なメモリ サイズは、クラスのロード後に決定できます。オブジェクトに領域を割り当てるタスクは、Java ヒープから特定のサイズのメモリを分割することに相当します。
割り当て方法には、ポインタ衝突とフリーリストの 2 つがあります。どちらの方法を選択するかは、Java ヒープが正規であるかどうかによって決まり、Java ヒープが正規であるかどうかは、ガベージ コレクターが圧縮機能を持っているかどうかによって決まります (コピー アルゴリズムとマーク クリーニング アルゴリズムは正規であり、マーククリアアルゴリズムは規則的ではありません)。
ここに画像の説明を挿入

メモリ割り当ての同時実行の問題

CAS は再試行に失敗します。CAS は目的ロックの実装です。
TLAB: 各スレッドに対して Eden のメモリ ブロックを事前に割り当てます。JVM がスレッド内のオブジェクトにメモリを割り当てるとき、まず TLAB に割り当てます。十分でない場合は、CAS を使用して割り当てます。
ゼロ値の初期化: メモリ割り当てが完了すると、仮想マシンは割り当てられるメモリ空間をゼロ値に初期化します (オブジェクト ヘッダーを除く)。この手順により、Java で初期値を割り当てずにオブジェクト インスタンスを直接使用できるようになります。
オブジェクト ヘッダーの設定: ゼロ値の初期化が完了した後、仮想マシンはオブジェクトに対して必要な設定を実行する必要があります。たとえば、オブジェクトのハッシュ コード、オブジェクトの GC 世代の年齢情報、バイアスされたロックなどの情報は、オブジェクト ヘッダーに配置されます。
init メソッドを実行します。上記の作業が完了すると、仮想マシンの観点からは新しいオブジェクトが生成されます。次に、init メソッドを実行して、プログラマの希望に従ってオブジェクトを初期化します。
オブジェクトの構成
HotSpot 仮想マシンでは、メモリ内のオブジェクトのレイアウトは、オブジェクト ヘッダー、インスタンス データ、および位置合わせパディングの 3 つの領域に分割できます。

オブジェクト ヘッダーには 2 つの情報部分が含まれています。最初の部分はオブジェクト自体の実行時データ (ハッシュ コード、GC 世代数、ロック ステータス フラグ) を格納するために使用され、もう 1 つの部分は型ポインターです。そのクラスのメタデータ、仮想マシンはこのポインタを使用して、オブジェクトがどのクラスのインスタンスであるかを判断します。

インスタンス データ セクションに保存されているオブジェクトに関する有効な情報。

これは、塗りつぶしのプレースホルダーとして機能します。

オブジェクトのアクセスアンカー
ハンドル。
ダイレクトポインタ。
補足
String str1 = "abcd";
String str2 = new String("abcd");
System.out.println(str1==str2);//falseこれら 2 つのメソッドで作成されたオブジェクトの
コピーには違いがあります。
1 つの方法は定数プール内にあり、2 番目の方法はヒープ メモリ内にあります。
ここに画像の説明を挿入

二重引用符宣言を使用して直接作成された String オブジェクトは、定数プールに直接格納されます。

定数プールによって宣言された String オブジェクトを使用しない場合は、String によって提供される intern メソッドを使用できます。String.intern() はネイティブ メソッドです。その機能は次のとおりです: 定数プールにコンテンツと等しい文字列が既に含まれている場合実行時にこの String オブジェクトの文字列を取得し、定数プール内の文字列の参照を返します。そうでない場合は、この String と同じ内容の文字列を定数プール内に作成し、定数プール内に作成された文字列の参照を返します。

String s1 = new String("Computer");
String s2 = s1.intern();
String s3 = "Computer";
System.out.println(s2);//Computer
System.out.println(s1 == s2) ;//false、一方はヒープ メモリ内の String オブジェクト、もう一方は定数プール System.out.println(s3 == s2) 内の String オブジェクトであるため
、//true、両方ともヒープ メモリ内の String オブジェクトであるため定数プール
のコピー
String str1 = "str";
String str2 = "ing";

String str3 = "str" + "ing";//定数プール内のオブジェクト
String str4 = str1 + str2; //ヒープ上に作成される新しいオブジェクト
String str5 = "string";//定数プール内のオブジェクト
システム.out.println(str3 == str4);//false
System.out.println(str3 == str5);//true
System.out.println(str4 == str5);//false 文字列
をコピー
s1 = 新しい文字列("abc"); この文の中でオブジェクトはいくつ作成されますか?

まず、文字列「abc」が定数プールに入れられ、次に新しい文字列「abc」が Java ヒープに入れられます。スタック上の参照は、ヒープ上のオブジェクトを指します。

Java の基本型のラッパー クラスのほとんどは、定数プール テクノロジ、つまり Byte、Short、Integer、Long、Character、Boolean を実装しています。Boolean を除く 5 つのラッパー クラスはすべて、デフォルトで [-128 127] キャッシュ データを作成し、この範囲を超えても新しいオブジェクトが作成されます。Float と Double は定数プール テクノロジを実装しません。

Integer i1 = 33;
Integer i2 = 33;
System.out.println(i1 == i2);// true を出力
Integer i11 = 333;
Integer i22 = 333;
System.out.println(i11 == i22);// Output false
Double i3 = 1.2;
Double i4 = 1.2;
System.out.println(i3 == i4);// false を
出力し、
Integer i1=40 をコピーします; Java は、コンパイル時にコードを Integer i1=Integer として直接カプセル化します。 (40); 定数プール内のオブジェクトを使用します。
Integer i1 = new Integer(40); この場合、新しいオブジェクトが作成されます。
整数 i1 = 40;
整数 i2 = new Integer(40);
System.out.println(i1==i2);// 出力 false

おすすめ

転載: blog.csdn.net/m0_54861649/article/details/126371631