目次
今回は、JVM ランタイム データ領域の Java 仮想マシン スタック部分について学習します。
1. メモリにスタックする
スタックは実行時の単位であり、ヒープはストレージの単位であり、プログラムの実行方法とデータの処理方法が決まります。ヒープは、データをどこにどのように配置するかというデータ ストレージの問題を解決します。
2. 基本的な内容
- Java 仮想マシン スタック (初期の頃は Java スタックとも呼ばれていました) は、各スレッドの作成時に仮想マシン スタックを作成し、次々と Java メソッドの呼び出しに対応するスタック フレームを内部に格納します。
- ライフサイクルとスレッドの一貫性
- Java プログラムの実行、メソッドのローカル変数 (8 つの基本データ型、オブジェクトの参照アドレス)、部分的な結果の保存、メソッドの呼び出しと戻りへの参加を担当します。
- ローカル変数とメンバー変数
- 基本データ型 VS 参照型変数 (クラス、配列、インターフェイス)
3. 利点
1. 高速かつ効率的なストレージ方式、プログラムカウンターに次ぐアクセス速度
2. JVM は、JAVA スタック上で 2 つの直接操作のみを実行します。
- 各メソッドはプッシュ(プッシュ、プッシュ)を伴い実行されます。
- 実行終了時にスタックをポップする
3. スタックにはガベージ コレクションはありませんが、OOM は存在します。
4. Java スタック サイズは動的または固定です。動的拡張の場合、OOMに十分なメモリを確保できず、固定値であり、スレッドが要求するスタック容量が固定値を超えるとStackOverflowErrorが発生します。
5. -Xss (メモリ: 小規模な操作、スタック Xss の略) を使用して、スレッドの最大スタック領域を設定します。
4. スタックの記憶単位
- 各スレッドには独自のスタックがあり、スタック内のデータはスタック フレーム形式で保存されます。
- スレッド上で実行される各メソッドはスタック フレームに対応します。
- スタック フレームはメモリ ブロックであり、メソッド実行中にさまざまなデータ情報を保持するデータ セットです。
- 先入れ、後出し、後入れ、先出し
- アクティブなスレッドでは、ある時点でアクティブなスタック フレームは 1 つだけです。現在実行中のメソッドの最上位スタック フレームのみが有効です。これをカレント スタック フレームと呼びます。対応するメソッドがカレント メソッドであり、対応するクラスがカレント クラスです。
- 実行エンジンによって実行されるすべてのバイトコード命令は、現在のスタック フレーム上でのみ動作します。
- メソッド内で他のメソッドが呼び出された場合、対応する新しいスタック フレームが作成され、先頭に配置され、新しい現在のフレームになります。
5. スタックの動作原理
異なるスレッドに含まれるスタック フレームが相互に参照することはできません。
現在のメソッドは他のメソッドを呼び出します。メソッドが戻ると、現在のスタック フレームはこのメソッドの実行結果を前のスタック フレームに返します。 の場合、仮想マシンは現在のスタック フレームを破棄し、以前のスタック フレームを新しいスタック フレームにします。
Java メソッドには 2 つの戻りメソッドがあります
- 1 つは、return 命令を使用した通常の関数 return です。
- もう 1 つは例外をスローする方法で、どちらの方法でもスタック フレームがポップされます。
6. スタックの内部構造
スタック フレームはスタックに格納され、各スタック フレームには以下が格納されます。
①ローカル変数テーブル
②オペランドスタック
③ダイナミックリンク
④メソッド返送先アドレス
⑤ 追加情報
(1) ローカル変数テーブル
1. 数値配列として定義され、主にメソッド本体内で定義されたメソッド パラメータとローカル変数を格納するために使用されます。データ型には、さまざまな基本データ型、オブジェクト参照、戻りアドレス型が含まれます。
2. ローカル変数テーブルはスレッドのスタック上に構築され、スレッドに対してプライベートであるため、データのセキュリティの問題はありません。
3. ローカル変数テーブルの容量はコンパイル時に決定されます。
4. ローカル変数テーブルには、コンパイル時に既知の各種基本データ型(8 種類)、参照型(reference)、リターンアドレス型が格納されます。
5. 最も基本的な記憶単位はスロットであり、32 ビットタイプは 1 スロット、64 ビットタイプ (ロングおよびダブル) は 2 スロットを占有します。
6. ローカル変数テーブルの変数は現在のメソッド呼び出しでのみ有効であり、仮想マシンはローカル変数テーブルを使用してパラメータ値のパラメータ変数リストへの転送プロセスを完了します。メソッド呼び出しが完了すると、メソッドスタックフレームが破棄されるため、ローカル変数テーブルも破棄されます。
7. スロットの理解
- JVM 仮想マシンは、ローカル変数テーブルの各スロットにアクセス インデックスを割り当て、このインデックスを通じて、ローカル変数テーブルで指定されたローカル変数値に正常にアクセスできます。
- 現在のフレームがコンストラクターまたはインスタンス メソッドによって作成されている場合、オブジェクトはこれを参照し、インデックス 0 のスロットに格納され、パラメーター リストの残りの部分は引き続き順番に配置されます。
補充:
スタック フレーム内でパフォーマンス チューニングに最も密接に関係する部分はローカル変数テーブルです。メソッドが実行されると、仮想マシンはローカル変数テーブル補完メソッド転送を使用します
ローカル変数テーブル内の変数も重要なガベージ コレクション ルート ノードであり、オブジェクトがローカル変数テーブル内で直接または間接的に参照されている限り、再利用されません。
(2) オペランドスタック
メソッドの実行中、データはバイトコード命令、つまりプッシュ/ポップに従ってスタックに書き込まれたりスタックから抽出されます。
1. 呼び出されたメソッドに戻り値がある場合、その戻り値は現在のスタック フレームのオペランド スタックにプッシュされ、プログラム カウンタは実行する必要がある次のバイトコード命令で更新されます。
2. Java 仮想マシンの解釈エンジンはスタックベースの実行エンジンであり、スタックはオペランド スタックです。
3. 主に計算プロセスの中間結果を保存するために使用され、計算プロセス中の変数の一時的な保管スペースとしても機能します。
4. メソッドが最初に実行を開始するとき、新しいスタック フレームが作成され、このメソッドのオペランド スタックは空です。
5. 各オペランド スタックには、値を格納するための明確なスタック深さがあり、最大深さはコンパイル時に定義されます。
6. スタック内では、32 ビットタイプはスタックユニット深さ 1 つを占有し、64 ビットタイプはスタックユニット深さ 2 つを占有します (ローカル変数テーブルのスロットストレージと同じ)
7. オペランド スタックは、データ アクセスにアクセス インデックス方式を使用せず、標準のプッシュおよびポップ操作を通じて 1 回のデータ アクセスのみを完了できます。
8、スタックトップ キャッシュ テクノロジー
オペランドはメモリに格納されるため、メモリの読み取りおよび書き込み操作が頻繁に行われると、実行速度に影響します。スタックの最上位要素はすべて物理 CPU のレジスタにキャッシュされるため、メモリへの読み取りおよび書き込みの回数が減り、パフォーマンスが向上します。実行エンジンの実行効率。
(3) ダイナミックリンク
実行時定数プールを指すメソッド参照 (実行時定数プールはメソッド領域に配置されます)
- 各スタック フレームには、フレームが属するランタイム定数プール内のメソッドへの参照が含まれています。
- 目的は、invokedynamic 命令などの動的リンクを実現するために現在のメソッドのコードをサポートすることです。
- Java ソース ファイルがバイトコード ファイルにコンパイルされると、すべての変数とメソッド参照がシンボル参照として使用され、クラス ファイルの定数プールに格納されます。
- 別のメソッドを呼び出すメソッドを記述する場合、それは定数プール内のメソッドを指すシンボリック参照によって表されます。
- 動的リンクの機能は、これらのシンボリック参照を呼び出しメソッドへの直接参照に変換することです。
定数プール、ランタイム定数プール
- 定数プールはバイトコード ファイル内にあり、ランタイム定数プールはランタイムのメソッド領域内にあります。
(4) メソッドの戻りアドレス
1. このメソッドを呼び出す PC レジスタの値を格納します
2. メソッドの終了
- 通常の実行が完了しました
- 未処理の例外が発生し、異常終了しました。
3. どのメソッドを終了しても、メソッドが終了すると、メソッドが呼び出された場所に戻ります。メソッドが正常に終了すると、呼び出し元の PC カウンタの値が戻りアドレス、つまりメソッドを呼び出した命令の次の命令のアドレスとして使用されます。
4. 異常終了の場合、戻りアドレスは例外テーブルによって決定されますが、この部分の情報は通常スタック フレームには保存されません。
5. 実行エンジンがメソッドによって返されたバイトコード命令 (リターン) を検出すると、戻り値は上位のメソッド呼び出し元に渡されます。これは、通常完了出口と呼ばれます。返品手順には次のものが含まれます。
- ireturn の戻り値が boolean、byte、char、short、int 型の場合に使用されます。
- lreturn——長い
- dreturn—double
- areturn - 参照型
- void として宣言されたメソッド、インスタンス初期化メソッド、クラスおよびインターフェイスの初期化メソッドに対するリターン ポインタもあります。
6. 本質的に、メソッドの終了は、現在のスタック フレームをポップするプロセスです。このとき、上位層メソッドのローカル変数テーブルやオペランドスタックを復元したり、戻り値を呼び出し元のスタックフレームのオペランドスタックにプッシュしたり、PCレジスタの値を設定したりするなど、呼び出し元が正しく動作するようにする必要があります。メソッドは引き続き実行できます。
7. 正常終了出口と異常終了出口の違いは、例外終了出口で終了すると、上位の呼び出し元に戻り値が生成されないことです。
(5) 追加情報
プログラムのデバッグをサポートする情報など、Java 仮想マシンの実装に関連する追加情報を含めることができます。すべてのスタック フレームにこれがあるわけではないため、スタック フレームには主にローカル変数テーブル、オペランド スタック、ダイナミック リンク、およびメソッドの戻りアドレスが含まれているというのが実際には正しいです。
(6) メソッド呼び出し
1. リンク方法
① 静的リンク
バイトコード ファイルが JVM にロードされるとき、呼び出されたターゲット メソッドがコンパイル時に既知で、実行時に変更されない場合、呼び出し元のシンボリック参照を直接参照に変換するプロセスは静的リンクと呼ばれます。
②ダイナミックリンク
呼び出されるメソッドがコンパイル時に決定できない場合、呼び出されるメソッドのシンボリック参照は実行時に直接参照に変換することしかできません。この参照変換プロセスは動的であるため、動的リンクと呼ばれます。
2. メソッドのバインド
バインディングは、フィールド、メソッド、またはクラスのシンボリック参照を直接参照に置き換えるプロセスです。一度だけ起こります。
①アーリーバインディング
呼び出されるターゲット メソッドはコンパイル時に認識され、実行時に変更されません。
②レイトバインディング
呼び出されるメソッドはコンパイル時には決定できず、関連するメソッドはプログラムの実行中に実際の型に従ってのみバインドできます。
Java の通常のメソッドはどれも仮想関数の特性 (実行時に確認され、遅延バインディングの特性を持つ) を持ちますが、C++ ではキーワード virtual を使用して明示的に定義します。
Java プログラムの仮想関数の特性をメソッドに持たせたくない場合は、キーワード Final を使用してメソッドをマークできます。
3. 仮想メソッドと非仮想メソッド
①非バーチャル方式
メソッドがコンパイル時に特定の呼び出しバージョンを決定する場合、このバージョンは実行時に不変になります。このようなメソッドは非仮想メソッドと呼ばれます
静的メソッド、プライベート メソッド、最終メソッド、インスタンス コンストラクター、および親クラス メソッドはすべて非仮想メソッドです。
② 非仮想メソッドに加えて、その他のメソッドを仮想メソッドと呼びます
4. メソッド呼び出し命令
① 通常の通話指示
- invokestatic は静的メソッドを呼び出します。一意のメソッドのバージョンは解析フェーズ中に決定されます。
- invokespecial は、<init> メソッド、プライベート クラス メソッド、および親クラス メソッドを呼び出し、解析フェーズ中に一意のメソッド バージョンを決定します。
- invovirtual すべての仮想メソッドを呼び出します
- invokeinterface インターフェースメソッドの呼び出し
- このうち、invokestatic命令とinvokespecial命令で呼び出されるメソッドを非仮想メソッドと呼び、それ以外(finalで変更されたメソッドを除く)を仮想メソッドと呼びます。
② 動的呼び出し命令
- invokedynamic は、呼び出す必要があるメソッドを動的に解析して実行します。
Java 8 で Lambda 式が登場するまで、Java で invokedynamic 命令を直接生成する方法が存在しませんでした。 Java 自体は静的言語であり、動的呼び出し命令は Java に動的言語の特性を与えます。
5. メソッド書き換えの本質
① オペランド スタックの先頭にある最初の要素によって実行されるオブジェクトの実際の型を見つけます (C として記録されます)。
② 定数プール内の記述子と単純名の両方に一致するタイプ C のメソッドが見つかった場合、アクセス許可の検証が行われ、合格した場合はメソッドへの直接参照が返され、検索処理が終了します。パスしない場合は、java が返されます。
③ それ以外の場合は、C の各親クラスに対して、継承関係に従って下から上に先の検索検証処理を実行します。
④ 適切なメソッドが見つからない場合は、java.lang.AbstractMethodError 例外がスローされます。
6. 仮想メソッドテーブル
オブジェクト指向プログラミングでは動的割り当てが頻繁に使用されますが、動的割り当て処理ごとにクラスのメソッド メタデータ内で適切なターゲットを新たに検索する必要がある場合、実行効率に影響を及ぼす可能性があります。そのため、パフォーマンスを向上させるために、JVM はクラスのメソッド領域に仮想メソッドテーブルを作成し、検索の代わりにインデックステーブルを使用します
各クラスには仮想メソッド テーブルがあり、テーブルには各メソッドの実際のエントリが保存されます
仮想メソッド テーブルは、クラスのロードのリンク フェーズ中に作成および初期化され、クラス変数の初期値が準備された後、JVM はクラスのメソッドも初期化します。