記事ディレクトリ
1. JVM実行プロセス
JVM は Java プログラムの動作基盤および動作環境であり、"一次编译,到处运行"
Java 実装の鍵でもあります。したがって、Java プログラミング言語を学習して理解するには、JVM を深く理解することが重要ですが、JVM は正確にどのように機能するのでしょうか?
次の図は、JVM の基本的な操作プロセスを示しています。
JVM の実行プロセスには、次の主要コンポーネントが含まれます。
-
Java コードをバイトコードに変換 (
.class
ファイル).class
: Java ソース コードを作成した後、Java コンパイラによって Java ソース コードをバイトコードにコンパイルして、Java プログラムの中間コードを含むファイルを生成する必要があります。 -
クラス ローダー (ClassLoader) : JVM のクラス ローダーは、
.class
バイトコード ファイルをメモリにロードする役割を果たします运行时数据区
。クラス ローダーは、クラスの完全修飾名 (Fully Qualified Name) に従って、対応するバイトコード ファイルを検索してロードし、ファイルの内容に従って、後続の実行エンジン呼び出しでこのクラスを表す Class オブジェクトを作成します。 -
実行メモリ管理: JVM は、プログラムの実行時に、主に次の領域を含むメモリ領域を管理します。
- 仮想マシン スタック (Java 仮想マシン スタック) : ローカル変数、オペランド スタックなどを含む、メソッド呼び出しを格納するために使用されるスタック フレーム。
- ネイティブ メソッド スタック: ネイティブ メソッドの実行に使用されるスタック。
- メソッド領域: クラスの構造情報、定数プール、静的変数などを格納します。
- ヒープ (Heap) : オブジェクトのインスタンスと配列を格納するメモリ領域。
- プログラム カウンター (プログラム カウンター) : 現在のスレッドによって実行されたバイトコード命令のアドレスまたはインデックスを記録します。
-
実行エンジン:実行エンジンは JVM のコア コンポーネントの 1 つで、メモリにロードされたバイトコード ファイルの実行を担当します。実行エンジンは次の 2 つの方法でバイトコードを実行します。
- 解釈と実行:バイトコード命令を 1 つずつ解釈し、対応する操作を実行します。解釈と実行は効率的ではありませんが、クロスプラットフォームであり、実行を開始したばかりのコード セグメントまたは実行頻度が低いコード セグメントに適しています。
- コンパイルと実行:バイトコードを特定のプラットフォーム用のネイティブ コードにコンパイルし、実行のために CPU に渡します。コンパイルと実行の効率は高くなりますが、追加のコンパイル時間が必要となるため、実行回数が頻繁にあるコードセグメントに適しています。
-
ネイティブ ライブラリ: バイトコードはクロスプラットフォームの命令セット仕様のセットにすぎないため、JVM 内の Java コードは基礎となるオペレーティング システムに直接アクセスできません。Java コードが基礎となるオペレーティング システムを実行する必要がある場合、またはネイティブ コード (C、C++ など) と対話する必要がある場合。これらの機能は、ローカル メソッド ライブラリを通じて実現する必要があります。ネイティブメソッドライブラリは、Javaプログラムからオペレーティングシステムに関連するネイティブコードを呼び出すことにより、プログラム全体の機能を実現します。
上記は、JVM の主な実行プロセスとランタイムに関与するコンポーネントです。下文是对其中的运行时数据区的详细介绍。
2. 仮想マシンスタック (スレッドプライベート)
仮想マシン スタックは Java スレッド専用のメモリ領域であり、スレッドのメソッド呼び出しやローカル変数などの情報を保存するために使用されます。仮想マシン スタックのライフ サイクルは、スレッドのライフ サイクルと同じです。仮想マシン スタックは、Java メソッド実行のメモリ モデルを記述します。
- 各メソッドが実行されると、ローカル変数テーブル、オペランドスタック、ダイナミックリンク、メソッド出口などの情報を格納するスタックフレーム(Stack Frame)が作成されます。
- メソッドが終了すると、対応するスタック フレームもスタックをポップすることによって破棄されます。
仮想マシン スタックは主に次の 4 つの部分で構成されます。
- ローカル変数テーブル:メソッドのローカル変数を保存するために使用されます。Javaメソッドが実行されると、メソッドパラメータやメソッド内で定義されたローカル変数を格納するためのメモリ領域が割り当てられます。ローカル変数テーブルのサイズはコンパイル中に決定され、格納されるデータ型には基本データ型とオブジェクト参照が含まれます。
- 操作スタック:メソッドの実行時に操作を計算するために使用されます。Java 仮想マシンのバイトコード命令は通常、オペランド スタックに基づいています。メソッドが呼び出されると、パラメータ値と戻り値がオペランド スタックにプッシュされ、メソッドが実行されると、バイトコード命令が計算のためにオペランド スタックから値をフェッチします。
- ダイナミック リンク:実行時定数プール内でスタック フレームが属するメソッドへの参照を指すために使用されます。Java 仮想マシンの実行時定数プールには、各クラスのメソッドのシンボリック参照が格納され、ダイナミック リンクはこれらのシンボリック参照を実際のメモリ アドレスに関連付けます。
- メソッド戻りアドレス:メソッドの戻りアドレスを示すために使用されます。メソッドの実行が完了したら、どこで実行を継続するかを知る必要があり、メソッドの戻りアドレスは戻り先のアドレスを記録するために使用されます。
プライベートスレッドとは:
- スレッド プライベートとは、マルチスレッド環境において、各スレッドが独自の独立したメモリ領域を持ち、他のスレッドがこの領域に直接アクセスしたり変更したりできないことを意味します。スレッド専用メモリ内のデータはスレッドごとに独立しており、互いに影響を与えません。この設計により、各スレッドが独立して実行され、独自のデータを維持しながら、マルチスレッド プログラムを同時に実行できるようになります。
- JVM では、
虚拟机栈
、本地方法栈
、 は程序计数器
スレッドプライベート メモリ領域 です。各スレッドには、メソッドの呼び出しと実行をサポートするための独自の仮想マシン スタックとローカル メソッド スタックがあり、これらのスタックのデータは他のスレッドからは見えません。プログラム カウンタもスレッド プライベートであり、各スレッドには独自のプログラム カウンタがあり、現在のスレッドによって実行されるバイトコード命令のアドレスまたはインデックスを示すために使用されます。スレッドが切り替わると、プログラム カウンタの値が保存および復元され、スレッドが切り替わった後もスレッドが正しく実行を継続できるようになります。堆
およびなどの他の共有メモリ領域は方法区(元空间)
スレッドによって共有されます。ヒープは Java オブジェクトのインスタンスと配列を格納するために使用され、メソッド領域はクラス構造情報、定数プール、静的変数などを格納するために使用されます。複数のスレッドがヒープおよびメソッド領域内のデータに共同でアクセスできるため、マルチスレッド環境では、共有データの一貫性と正確性を保護するために同期メカニズムが必要です。- メモリ領域はスレッドのプライベートと共有によって分割されます。
3. ローカルメソッドスタック(スレッドプライベート)
ネイティブ メソッド スタックは仮想マシン スタックに似ており、Java スレッド専用のメモリ領域でもあります。Java プログラムがネイティブ メソッド (ネイティブ メソッド) を呼び出して実行することをサポートするために使用されます。ネイティブ メソッドは、他の言語 (C、C++ など) で記述されたメソッドであり、ネイティブ メソッド インターフェイス (JNI、Java Native Interface) を通じて Java コードと対話します。ネイティブ メソッド スタックと仮想マシン スタックも機能は似ていますが、それぞれ Java メソッドとネイティブ メソッドの呼び出しに使用されます。
4. メソッド領域(メタデータ領域)
メソッド領域は、 Java スレッドによって共有されるメモリ領域であり、クラス、定数プール、静的変数、ジャストインタイム コンパイラによってコンパイルされたコードなどの構造情報を格納するために使用されます。JDK 8 以前のバージョンでは、メソッド領域は です永久代(Permanent Generation)
。一方、JDK 8 以降のバージョンでは、メソッド領域は に置き換えられます元空间(Metaspace)
。メソッド領域のサイズは、JVM起動時のパラメータで設定できます。
「Java仮想マシン仕様」では、この領域を「メソッド領域」と呼び、HotSpot仮想マシンの実装では、この領域をJDK 7ではPermanent Generation(Permanent Generation)、JDK 8ではMetaspace(メタスペース)と呼んでいます。 。
JDK 1.8 メタスペースの変更点:
- HotSpot の場合、JDK 8 メタスペースのメモリはローカル メモリに属するため、メタスペースのサイズは JVM の最大メモリのパラメータの影響を受けなくなり、ローカル メモリのサイズに関係します。
- JDK 8 では、文字列定数プールがヒープに移動されました。
ランタイム定数プール:
ランタイム定数プール (Runtime Constant Pool) はメソッド領域の一部であり、コンパイル中に生成されるさまざまなリテラルおよびシンボリック参照を格納するために使用されます。これは、クラスのロード プロセス中に、バイトコード ファイル内の定数プール テーブルに従って仮想マシンによって構築されます。
ランタイム定数プールには、主に 2 種類のデータがあります。
-
リテラル:
- 文字列リテラル: Java プログラムに直接書き込まれた文字列値 (例: "Hello, Java")。JDK 8 では、文字列リテラルはヒープに移動されるため、ガベージ コレクションも実行でき、一部のメモリの問題が回避されます。
final
定数: コンパイル中に決定できる定数final
。例:final int MAX_VALUE = 100;
。- 基本データ型の値: 例: 整数、浮動小数点数、文字などの基本データ型のリテラル。
-
シンボリック参照:
シンボリック参照はコンパイル中に生成されるデータ構造ですが、参照先のターゲットを記述するためにクラスの読み込みフェーズ中に使用する必要があります。シンボリック参照には次のものが含まれます。- クラスとインターフェイスの完全修飾名: 例:
java.lang.String
。 - フィールド名と記述子: フィールドの名前とデータ型を説明するために使用されます。例:
int count
。 - メソッド名と記述子: メソッド名、パラメータ リスト、戻り値のタイプを説明するために使用されます。例:
void print(String message)
。
- クラスとインターフェイスの完全修飾名: 例:
実行時定数プールは、クラスがロードされた後にメソッド領域に格納され、クラスのライフサイクル全体にわたって存在し、クラス自体とともにリサイクルされます。定数プール分析、動的リンク、プログラム実行時のメソッド呼び出しなどの機能を提供し、Java プログラムの実行に必要なサポートを提供します。
5. ヒープ (スレッド共有)
ヒープ (Heap) は、Java オブジェクトのインスタンスと配列を格納するための Java 仮想マシン内の実行時データ領域です。ヒープはJVM 内の最大のメモリ領域であり、すべてのスレッドによって共有される唯一のメモリ領域です。
Java プログラムの実行中、new
キーワードを使用してオブジェクトを作成すると、オブジェクト インスタンスがヒープに割り当てられます。同時に、配列はオブジェクトでもあるため、配列の要素もヒープに格納されます。ヒープのサイズは、JVM の起動時にパラメータで指定することも、動的に調整することもできます (サイズが指定されていない場合、JVM はシステム メモリに従って初期サイズを自動的に設定します)。
ヒープの主な機能は次のとおりです。
-
スレッド共有: ヒープは、すべてのスレッドによって共有されるメモリ領域です。すべてのスレッドはヒープ内のオブジェクト インスタンスにアクセスできるため、複数のスレッドがオブジェクトを共同で操作および共有できるようになります。
-
動的割り当てとリサイクル: プログラムの実行中にヒープのサイズを動的に調整できます。オブジェクトが作成されると、JVM は自動的にヒープ内にメモリ領域を割り当てます。ガベージ コレクターは、オブジェクトが参照されなくなったときに、ヒープ内の未使用のオブジェクトのメモリを自動的に再利用します。
-
ガベージ コレクション: ヒープ内のメモリはガベージ コレクターによって管理されます。ガベージ コレクターは、ヒープ内のオブジェクトを定期的にチェックし、参照されなくなったオブジェクトをガベージとしてマークし、これらのガベージ オブジェクトのメモリを再利用して、他のオブジェクトが使用できるようにヒープに解放します。
-
自動メモリ管理: Java のヒープ メモリは JVM によって自動的に管理されるため、プログラマが手動でメモリを解放する必要はありません。JVM はガベージ コレクションを自動的に実行して未使用のメモリを解放し、メモリ リークなどの問題を回避します。
さらに、Java 仮想マシンのヒープ メモリは、通常、新生代(Young Generation)
と の老生代(Old Generation)
2 つの主要な領域に分割されます。
-
若い世代:
若い世代は、新しく作成されたオブジェクトが保存される領域です。新しい世代では、ヒープ メモリは通常、1 つの Eden スペースと 2 つの Survivor スペース (通常は S0 および S1 と呼ばれます) に分割されます。新しく作成されたオブジェクトは、最初に Eden 空間に割り当てられます。Eden スペースがいっぱいになると、マイナー GC (Young GC) がトリガーされ、ガベージ コレクターは Eden スペースとその中に残っているオブジェクトを未使用の Survivor スペースにコピーします。次に、ガベージ コレクターは使用中の Eden スペースと Survivor スペースをクリアし、その中のガベージ オブジェクトをリサイクルします。生き残ったオブジェクトは古い世代に昇格されます。 -
Old Generation (Old Generation) :
Old Generation は、長期間存続するオブジェクトを格納する領域です。古い世代に格納されたオブジェクトは、通常、新しい世代で一定数のマイナー GC が発生した後も存続するオブジェクト、つまりラージ オブジェクトです。古い世代のスペースがいっぱいになると、メジャー GC (フル GC) がトリガーされ、ガベージ コレクターは新しい世代と古い世代のすべてのオブジェクトを含むヒープ全体をリサイクルします。
ヒープメモリを新世代と旧世代に分割し、異なるガベージコレクション戦略を採用することで、ガベージコレクションの効率を向上させることができます。若い世代のマイナー GC は、ライフ サイクルの短いオブジェクトをリサイクルしてメモリをできるだけ早く解放するために頻繁に実行されますが、古い世代のメジャー GC は、ライフ サイクルの長いオブジェクトをリサイクルする頻度が比較的低く、安定性を確保します。古い世代と信頼性。
6. プログラムカウンター(スレッドプライベート)
プログラム カウンタは、Java スレッド専用のメモリ領域でもあります。これは、現在のスレッドが何を実行しているかを示すインジケーターです字节码指令的地址或索引
。Java スレッドが切り替わると、プログラム カウンタの値が保存および復元され、スレッドが切り替わった後もスレッドが正しく実行を継続できるようにします。