JAVAの基本的なJVMの説明

JVM アーキテクチャ

ここに画像の説明を挿入

ここに画像の説明を挿入 

これには主に 2 つのサブシステムと 2 つのコンポーネントが含まれています。

クラス ローダー (クラス ローダー) サブシステム (.class ファイルのロードに使用)。

実行エンジン (実行エンジン) サブシステム (バイトコードの実行、またはネイティブ メソッドの実行)。

実行時データ領域 (実行時データ領域) コンポーネント (メソッド領域、ヒープ、Java スタック、PC レジスタ、ローカル メソッド スタック)。

ネイティブ インターフェイス (ローカル インターフェイス) コンポーネント。

  • クラス ローダーサブシステム: 指定された完全修飾クラス名 (java.lang.Object など) に従って、クラス ファイルの内容をランタイム データ領域のメソッド領域 (メソッド領域) にロードします。

  • 実行エンジンサブシステム: クラス内の命令を実行します。メソッドのバイトコードは、Java 仮想マシンの一連の命令で構成されます。各命令は、1 バイトのオペコードとそれに続く 0 個以上のオペランドで構成されます。実行エンジンがバイトコードを実行するとき、最初にオペコードを取得し、オペコードにオペランドがある場合はそのオペランドを取得します。次のオペコードをフェッチする前に、オペコードと後続のオペランドで指定されたアクションを実行します。バイトコードを実行するこのプロセスは、スレッドが完了するまで継続されます。JVM 実装の中核は実行エンジンです。言い換えれば、Sun の JDK と IBM の JDK の品質は主に、実行エンジンのそれぞれの実装の品質に依存します。

  • ネイティブ インターフェイスコンポーネント: ネイティブ ライブラリと対話し、他のプログラミング言語と対話するためのインターフェイスです。Java でネイティブとして宣言されたメソッドのほとんどは、jdk/src/<platform>/native にあります。共有、つまりプラットフォームに依存しないコードにすることも、特定のプラットフォームにすることもできます。このネイティブ ディレクトリの構造は、Java ソース コードの構造と同様に、パッケージ名ごとに編成されています。ただし、これらのネイティブ メソッドは「JVM」ではなく、JVM 内にあるものではなく「クラス ライブラリ」であることに注意する必要があります。

  • ランタイム データ領域コンポーネント: 仮想マシンは、実行中のプログラムで使用される数種類のランタイム データ領域を定義します。ヒープ領域やメソッド領域など、その一部は仮想マシンの開始時に作成され、仮想マシンの終了時に破棄されます。 。2 つ目はスレッドと 1 対 1 の対応で、スタックやレジスタなどのスレッドの開始と終了に応じて作成および破棄されます。

JVMメモリモジュール

Java メモリ モデルは、マルチスレッドの同時実行条件下で共有変数の読み書き (実際には共有変数に対応するメモリ操作) を行うための Java 言語の仕様であり、主にマルチスレッドの可視性と原子性の問題を解決し、共有変数のマルチスレッド操作の競合の問題

JMM (Java メモリ モデル) Java メモリ モデル自体は抽象的な概念であり、現実のものではありません。これは、プログラム内の各変数 (インスタンス フィールド、静的フィールド、要素のアクセス モードを含む) を定義する一連のルールまたは仕様を記述します。配列オブジェクトを構成します)。

JMM は、スレッドとメイン メモリ間の抽象的な関係を定義します。スレッド間の共有変数はメイン メモリ (メイン メモリ) に格納され、各スレッドにはプライベート ローカル メモリ (ローカル メモリ) があり、ローカル メモリには共有のスレッドの読み取り/書き込みコピーが格納されます。変数。

JMM メモリ モデルの 3 つの主な特徴
1. 原子性
同期ミューテックス ロックを使用して、操作の原子性を確保します。
2. 可視性:
揮発性。変数自体とその時点の他の変数の状態がキャッシュから強制的にフラッシュされます。
同期されている場合、変数のロック解除操作を実行する前に、変数の値をメイン メモリに同期して戻す必要があります。
Final では、final キーワードによって変更されたフィールドがコンストラクターで初期化され、this エスケープが存在しない (他のスレッドがこの参照を通じて半分初期化されたオブジェクトにアクセスする) と、他のスレッドは Final フィールドの値を確認できます。
3.
ソースコードの順序 -> コンパイラ最適化再配置 -> 命令並列再配置 -> メモリシステム再配置 -> 最終実行コマンド。
並べ替えプロセスはシングルスレッド プログラムの実行には影響しませんが、マルチスレッドの同時実行の正確さには影響します。
プロセッサは再配置を実行する際にデータの依存関係を考慮する必要があります。マルチスレッド環境ではスレッドが交互に実行されます。コンパイラの最適化による再配置の存在により、2 つのスレッドで使用される変数が整合性を保証できるかどうかは不確実です。

JMM は、スレッドとメイン メモリ間の抽象的な関係を定義します。

スレッド間の共有変数はメイン メモリ (Main Memory) に保存され、
各スレッドにはプライベート ローカル メモリ (Local Memory) があります。ローカル メモリは JMM の抽象的な概念であり、実際には存在しません。キャッシュ、書き込みバッファ、レジスタが含まれます。 、その他のハードウェアとコンパイラの最適化。スレッドの読み取り/書き込み共有変数のコピーは、ローカル メモリに保存されます。
下位レベルから見ると、メイン メモリはハードウェア メモリであり、より良い動作速度を得るために、仮想マシンとハードウェア システムでは、最初に作業メモリをレジスタとキャッシュに保存できる場合があります。
Java メモリ モデルにおけるスレッドの作業メモリは、CPU のレジスタとキャッシュの抽象的な記述です。JVM の静的内部ストレージ モデル (JVM メモリ モデル) はメモリの物理的な分割にすぎず、メモリのみに限定され、JVM メモリのみに限定されます。

ヒープ割り当て:

 

JVMランタイムデータ領域

1 メソッド領域 MethodArea メソッド
領域(MethodArea)は、Java ヒープと同様に各スレッドが共有するメモリ領域で、型情報、定数、静的変数、インスタントコンパイラでコンパイルしたコードキャッシュなどのデータを格納します。仮想マシンによってロードされています。「Java 仮想マシン仕様」ではメソッド領域をヒープの論理部分として説明していますが、Java ヒープと区別するために「非ヒープ」という別名が付けられています。

ランタイム定数プール

ランタイム定数プール (RuntimeConstantPool) はメソッド領域の一部です。Class ファイルには、クラスのバージョン、フィールド、メソッド、インターフェイス、その他の記述情報に加えて、コンパイル中に生成されるさまざまなリテラルやシンボル参照を格納するために使用される定数プール テーブル (ConstantPoolTable) もあります。クラスがロードされると、メソッド領域のランタイム定数プールに保存されます。

Java 仮想マシンには、クラス ファイル (定数プールも当然含む) の各部分の形式について厳格な規定があり、たとえば、各バイトは、どのデータが認識されロードされる前に仕様の要件を満たさなければならないかを格納するために使用されます。仮想マシンによって実行されます。ただし、ランタイム定数プールについては、「Java 仮想マシン仕様」では詳細な要件が定められていません。さまざまなプロバイダーによって実装された仮想マシンは、独自のニーズに応じてこのメモリ領域を実装できますが、一般的に言えば、クラス ファイルの保存に加えて、説明したシンボリック参照に加えて、シンボリック参照から変換された直接参照も実行時定数プール [1] に格納されます。

クラス ファイル定数プールと比較したランタイム定数プールのもう 1 つの重要な特徴は、動的であることです。Java 言語では、コンパイル時のみに定数を生成する必要はありません。つまり、定数プールの内容は、コンパイル時に事前設定されていません。クラス ファイルを入力できます。メソッド領域のランタイム定数プールでは、ランタイム中に新しい定数をプールに入れることもできます。この機能は、String クラスの intern() メソッドよりも開発者によってよく使用されます。

実行時定数プールはメソッド領域の一部であるため、当然メソッド領域のメモリによって制限され、定数プールがメモリに適用できなくなると、OutOfMemoryError 例外がスローされます。

2 Java ヒープ ヒープ
Java アプリケーションの場合、Java ヒープ (JavaHeap) は仮想マシンによって管理される最大のメモリ部分です。Java ヒープは、すべてのスレッドによって共有されるメモリ領域であり、仮想マシンの起動時に作成されます。このメモリ領域の唯一の目的はオブジェクト インスタンスを格納することであり、Java 世界の「ほぼ」すべてのオブジェクト インスタンスがここにメモリを割り当てます。「Java 仮想マシン仕様」における Java ヒープの説明は、「すべてのオブジェクト インスタンスと配列はヒープ上に割り当てられるべきである」となっており、ここで著者が書いた「ほぼ」とは、実装の観点から次のことを意味します。 Java 言語 現時点だけを考えても、ジャス​​トインタイムコンパイル技術、特にますます強力になっているエスケープ解析技術の進歩により、将来的には値型のサポートが登場する可能性がある兆しが見えています。 - スタック割り当てとスカラー置換の最適化メソッドによって、いくつかの微妙な変更が静かに行われているため、Java オブジェクト インスタンスがすべてヒープ上に割り当てられていると言うのはそれほど絶対的ではありません。

Java ヒープはガベージ コレクターによって管理されるメモリ領域であるため、一部の資料では「GC ヒープ」とも呼ばれます。メモリ再利用の観点から見ると、最新のガベージ コレクターのほとんどは世代別コレクション理論に基づいて設計されているため、Java ヒープには「新世代」、「旧世代」、「永続世代」、「Eden 空間」、「FromSurvivor 空間」が頻繁に出現します。 「」「ToSurvivor 空間」およびその他の用語、これらの領域分割は一部のガベージ コレクターの共通の特性または設計スタイルにすぎず、Java 仮想マシンの特定の実装に固有のメモリ レイアウトではなく、ましてや「Java 仮想マシン仕様」ではありません。 Java ヒープのさらに詳細な分割。メモリ割り当ての観点から見ると、複数のスレッドプライベート割り当てバッファ (ThreadLocalAllocationBuffer、TLAB) をすべてのスレッドで共有される Java ヒープに分割して、オブジェクト割り当ての効率を向上させることができます。ただし、どの観点から見ても、どのように分割しても、Java ヒープ内の格納内容の共通性は変わりません。どの領域に格納しても、オブジェクトのインスタンスのみを格納できます。Java ヒープを細分化する目的は、メモリの再利用を改善するか、より速くメモリを割り当てるためだけに使用してください。

「Java 仮想マシン仕様」によると、Java ヒープは物理的に不連続なメモリ空間に存在することができますが、ファイルを保存するためにディスク領域を使用するのと同じように、論理的には連続していると見なされるべきであり、各ファイルは連続して保存される必要はありません。 。ただし、大きなオブジェクト (通常は配列オブジェクトなど) の場合、ほとんどの仮想マシン実装では、実装の簡素化とストレージの効率化のために連続メモリ スペースが必要になる可能性があります。

Java ヒープは固定サイズまたは拡張可能なサイズとして実装できますが、現在主流の Java 仮想マシンはすべて拡張可能なもの (パラメータ -Xmx および -Xms で設定) に従って実装されています。Java ヒープにインスタンスの割り当てを完了するためのメモリがなく、ヒープを拡張できなくなった場合、Java 仮想マシンは OutOfMemoryError 例外をスローします。

3 仮想マシン スタック
各スレッドの実行に必要なメモリ空間は仮想マシン スタックになります。
スレッドはプライベートであり、そのライフ サイクルはスレッドと一致します。
各スタックは、それぞれが占有するメモリに対応する複数のスタック フレームで構成されます。メソッド呼び出し;
各スレッドは、現在実行中のメソッドに対応するアクティブなスタック フレームを 1 つだけ持つことができます。
Java メソッド実行のメモリ モデルについて説明します。各メソッドは、実行時にローカル変数テーブル、オペランド スタック、ダイナミック リンク、メソッド出口などの情報を格納するスタック フレーム (スタック フレーム) を作成します。呼び出しから実行の終了まで、各メソッドは、スタック フレームが仮想マシン スタックからプッシュされ、スタックからポップされるまでのプロセスに対応します。

StackOverflowError: スレッドによって要求されたスタックの深さが、仮想マシンによって許可される深さを超えています。

OutOfMemoryError: 仮想マシン スタックは動的に拡張できますが、拡張時に十分なメモリを適用できない場合。

 

4 プログラム カウンタ
プログラム カウンタ (ProgramCounterRegister) は小さなメモリ空間で、現在のスレッドによって実行されるバイトコードの行番号インジケーターとみなすことができ、次に実行される命令コードを指し、実行エンジンは次の命令コードを読み取ります。命令コード命令。より正確には、スレッドの実行は、スレッドの正しい実行を保証するために、バイトコード インタプリタを通じて現在のスレッドのカウンタの値を変更し、実行する必要がある次のバイトコード命令を取得することです。

現在実行中の命令のアドレスを保持します。プログラムが実行されると、プログラム カウンタは次の命令に更新されます。

スレッド切り替え(コンテキスト切り替え)後に正しい実行位置を確実に復元できるように、各スレッドは独立したプログラムカウンタを持ち、各スレッドのカウンタは互いに影響を与えずに独立して格納されます言い換えれば、プログラム カウンタはスレッドのプライベート メモリです。

5 ネイティブ メソッド スタック
ネイティブ メソッド スタック (NativeMethodStacks) は、仮想マシン スタックと非常によく似ており、仮想マシンによって使用されるローカル (ネイティブ) メソッドを提供します

「Java 仮想マシン仕様」には、ローカル メソッド スタック内のメソッドの言語、使用法、およびデータ構造に関する強制的な規制はありません。

したがって、特定の仮想マシンはニーズに応じて自由に実装でき、一部の Java 仮想マシン (ホットスポット仮想マシンなど) では、ローカル メソッド スタックと仮想マシン スタックを 1 つに直接結合できます。仮想マシン スタックと同様に、ローカル メソッド スタックも、スタックの深さがオーバーフローしたり、スタックの拡張が失敗したりすると、StackOverflowError 例外と OutOfMemoryError 例外をスローします。

6 ダイレクト メモリ
ダイレクト メモリ (DirectMemory) は、仮想マシンのランタイム データ領域の一部ではなく、「Java 仮想マシン仕様」で定義されているメモリ領域でもありません。ただし、メモリのこの部分も頻繁に使用され、OutOfMemoryError 例外が発生する可能性もあるため、まとめて説明するためにここに記載します。

JDK1.4では新たにNIO(NewInput/Output)クラスが追加され、 Channel(チャンネル)とBuffer(バッファ)をベースとしたI/Oメソッドが導入され、Native関数ライブラリを利用してオフヒープメモリを直接確保できるようになりました。次に、このメモリへの参照として Java ヒープに格納されている DirectByteBuffer オブジェクトを介して操作します。これにより、Java ヒープとネイティブ ヒープの間でのデータのコピーが回避されるため、一部のシナリオではパフォーマンスが大幅に向上します

明らかに、マシンの直接メモリ割り当ては Java ヒープのサイズによって制限されませんが、これはメモリであるため、マシンの合計メモリ (物理メモリ、SWAP パーティション、ページング ファイルを含む) によって確実に制限されます。一般に、サーバー管理者が仮想マシンのパラメータを構成する場合、実際のメモリに基づいて -Xmx およびその他のパラメータ情報を設定しますが、多くの場合、直接メモリは無視されるため、各メモリ領域の合計は、実際のメモリ領域よりも大きくなります。物理メモリ制限 (物理およびオペレーティング システム レベルを含む) 制限)、動的拡張中に OutOfMemoryError 例外が発生します。

4. 実行エンジン
すべての Java 仮想マシンがインタプリタとコンパイラが共存する実行アーキテクチャを採用しているわけではありませんが、HotSpot、OpenJ9 など、現在主流の商用 Java 仮想マシンは、内部にインタプリタとコンパイラの両方を備えています。

HotSpot 仮想マシンには 2 つ (または 3 つ) のジャストインタイム コンパイラが組み込まれており、そのうちの 2 つは以前から存在しており、それぞれ「クライアント コンパイラ」、「サーバー コンパイラ」、または C1 コンパイラ、C2 コンパイラと呼ばれています。略して (C2 は一部の資料や JDK ソース コードでは Opto コンパイラーとも呼ばれます)、3 つ目は JDK10 でのみ登場した Graal コンパイラーであり、長期的な目標は C2 を置き換えることです。

ガベージ コレクション (GC)

手動ガベージ コレクションは不可能です。手動リマインダーのみで、JVM が GC
領域を自動的に再利用するのを待ちます。 JVM がヒープ (Heap) およびメソッド領域で GC を実行すると
、これら 3 つの領域 (新しい領域、存続領域、および古い領域)統合されていない リサイクル、リカバリは新世代
ライト GC (通常の GC) は新世代領域のみで、まれに生存領域に影響を与える (新世代領域がいっぱいの場合) ヘビー GC (グローバル GC) はメモリをグローバルに
解放
14. 一般的なガベージ コレクション アルゴリズム
1. 参照 カウント アルゴリズムの原理
は、オブジェクトが参照を持つこと、つまり、カウントが増加し、参照が削除され、カウントが減少することです。ガベージ コレクション中には、コレクション数が 0 のオブジェクトのみが使用されます。このアルゴリズムの最も致命的な問題は、循環参照を処理できないことです。

2. コピーアルゴリズム
メモリ空間を 2 つの等しい領域に分割し、一度に 1 つの領域だけを使用するアルゴリズムです。ガベージ コレクション中に、現在使用されている領域を走査し、使用中のオブジェクトを別の領域にコピーし、同時に未使用のオブジェクトをリサイクルします。このアルゴリズムは、毎回使用中のオブジェクトのみを処理するため、コピーのコストが比較的小さく、同時にコピー後に対応するメモリ構成を実行できます。
利点: 断片化の問題が発生しない
欠点: 2 倍のメモリ空間が必要となり、無駄が生じる

3. マーククリアアルゴリズム
このアルゴリズムは 2 段階で実行されます。最初のステージは参照ルート ノードから開始して、使用されている残りのオブジェクトをマークし、2 番目のステージはヒープ全体を走査してマークされていないオブジェクトをクリアします。
利点: メモリ領域の無駄がありません。
欠点: このアルゴリズムではアプリケーション全体を一時停止する必要があり、同時にメモリの断片化が発生します。

4. マーク圧縮アルゴリズム
このアルゴリズムは、「マーククリア」アルゴリズムと「コピー」アルゴリズムの利点を組み合わせたものです。また、2 つのフェーズに分かれています。最初のフェーズでは、ルート ノードから開始して、すべての生き残ったオブジェクトにマークを付けます。2 番目のフェーズでは、ヒープ全体を走査し、マークされていないオブジェクトをクリアし、生き残ったオブジェクトをヒープの 1 つに「圧縮」し、順番に放出します。 。
このアルゴリズムは、「マークスイープ」の断片化の問題を回避し、「コピー」アルゴリズムのスペースの問題も回避します。

世代リサイクル戦略

1. 新しく作成されたオブジェクトのほとんどは Eden 領域に格納されます。
2. 初めて Eden 領域がいっぱいになると、MinorGC (ライト GC) がトリガーされます。まず、Eden 領域のガベージ オブジェクトが回収されてクリアされ、残ったオブジェクトが S0 にコピーされます。この時点では S1 は空です。
3. 次回 Eden 領域がいっぱいになったら、再度ガベージ コレクションを実行します。今回は Eden 領域と S0 領域のすべてのガベージ オブジェクトがクリアされ、残ったオブジェクトは S1 にコピーされ、このとき S0 は空になります。 。
4. S0 と S1 を数回 (デフォルトは 15 回) 切り替えた後、残ったオブジェクトが古い世代に転送されます。
5. FullGC (フル GC) は、古い世代がいっぱいになったときにトリガーされます。MinorGC

使用されるアルゴリズムはコピー アルゴリズムです
。若い世代のヒープ領域が不足したときにトリガーされます。
フル コレクションと比較して収集間隔が短くなります。
FullGC
で使用されるアルゴリズムは通常、マーク圧縮アルゴリズムです
古い世代のヒープ領域がいっぱいになると、完全なコレクション操作がトリガーされます
。System.gc() メソッドを使用して、完全なコレクションを明示的に開始できます。完全
なコレクションは非常に優れています。時間がかかる

ここに画像の説明を書きます

おすすめ

転載: blog.csdn.net/hongyucai/article/details/130970444