仮想マシンのメモリ管理モデルの分析

Java プログラムを実行する過程で、Java 仮想マシンは、Java プログラムが管理するメモリをいくつかの異なるデータ領域に分割します。これらのデータ領域は、仮想マシン スタック、ヒープ、メソッド領域、ローカル メソッド スタック、プログラム カウンターの 5 つの部分に分割できます。 、図に示すように:

仮想マシン スタック

Java 仮想マシン スタック (Java 仮想マシン スタック) はスレッド専用であり、そのライフ サイクルはスレッドのライフ サイクルと同じです。つまり、各メソッドが実行されると、Java 仮想マシンはスタック フレーム (スタック フレーム) を同期的に作成し、ローカル変数テーブル、オペランド スタック、動的接続、メソッドの終了、およびその他の情報を格納します。各メソッドが呼び出されて実行が完了するまでのプロセスは、スタック フレームが仮想マシン スタックにプッシュされてからポップされるまでのプロセスに相当します。仮想マシン スタックの内容を説明しましょう。

ローカル変数テーブル

ローカル変数テーブルには、Java 仮想マシンのさまざまな基本データ型 (boolean、byte、char、short、int、float、long、double) とオブジェクト参照 (参照型。オブジェクト自体と同等ではなく、オブジェクトの開始アドレスを指す参照ポインタ、またはオブジェクトを表すハンドルまたはオブジェクトに関連する他の場所を指す場合があります) および returnAddress タイプ (バイトコード命令のアドレスを指す)。

ローカル変数テーブル内のこれらのデータ型のストレージ スペースは、ローカル変数スロット (Slot) によって表されます。このスロットでは、64 ビットの long と double 型のデータが 2 つの変数スロットを占有し、残りのデータ型だけが占有されます。一。ローカル変数テーブルに必要なメモリ空間はコンパイル中に割り当てられます. メソッドに入るとき, メソッドがスタックフレームに割り当てる必要があるローカル変数空間の量は完全に決定されます. ローカル変数テーブルのサイズはコンパイル中に変更されません.メソッドの実行. . ここで言及されている「サイズ」は、可変スロットの数、仮想マシンが実際に使用するメモリ領域の量 (たとえば、1 つの可変スロットが 32 ビット、64 ビット、またはそれ以上を占める) を参照して、可変スロットを実装することに注意してください。は、特定の仮想マシンの実装の裁量に完全に委ねられています。

returnAddress 型は現在ではまれですが、バイトコード命令 jsr、jsr_w、および ret を提供し、バイトコード命令のアドレスを指します。long と double は 2 つの変数スロットに割り当てられますが、スレッド内にあるため、データの競合やスレッド セーフの問題は発生しません。

オペランド スタック

オペランド スタック (Operand Stack) は操作スタックとも呼ばれ、後入れ先出し (Last In First Out、LIFO) スタックです。ローカル変数テーブルと同様に、オペランド スタックの最大深度もコンパイル時に Code 属性の max_stacks データ項目に書き込まれます。メソッドが実行を開始したばかりのとき、このメソッドのオペランド スタックは空です。メソッドの実行中、オペランド スタックからコンテンツを書き込んだり抽出したりするためのさまざまなバイトコード命令、つまり、ポップおよびプッシュ操作があります。例えば、算術演算を行う場合は、演算に関係するオペランドスタックをスタックの一番上にプッシュしてから演算命令を呼び出すことで実行されます.例えば、他のメソッドを呼び出す場合は、メソッドパラメータはオペランドスタックを介して渡されます. . たとえば、整数加算バイトコード命令 iadd など、この命令では、この命令の実行時に、オペランド スタック内のスタックの一番上に最も近い 2 つの要素が 2 つの int 値とともに格納されている必要があります。ポップされて追加され、追加の結果がスタックにプッシュバックされます。

小さなケースを書きます:

 
 

パッケージcom.courage; public class DeOperandStack { public static void main(String[] args) { int i = 1; int j = 2; int k = i + j; } }

現時点では、DeOperandStack クラスには 1 つのスレッド (メイン) しかなく、変数はローカル変数テーブルにあります。

デフォルトの args は変数 0 であるため、このスレッドにはローカル変数が 4 つあるため、オペランド スタックを使用して加算と減算を行うにはどうすればよいですか?

最初の定数をスタックにプッシュし、1 番目の変数をローカル変数テーブルに格納し、次に 2 番目の定数をスタックにプッシュし、2 番目の変数をローカル変数テーブルに格納し、2 つの値をロードします。ローカル変数テーブル1と2のスタックをプッシュし、合計をポップして結果をスタックにプッシュし、スタックの先頭データを3番目の変数に格納します。

動的リンク

各スタック フレームには、ランタイム定数プール内でスタック フレームが属するメソッドへの参照が含まれています。この参照は、メソッド呼び出し中の動的リンク (Dynamic Linking) をサポートするために保持されます。Class ファイルの定数プールには多数のシンボリック参照が格納されており、バイトコード内のメソッド呼び出し命令は、定数プール内のメソッドを指すシンボリック参照をパラメーターとして使用しています。これらのシンボリック参照の一部は、クラスのロード段階または初めて使用されるときに直接参照に変換されます. この変換は静的解決と呼ばれます. 他の部分は、各実行中に直接参照に変換されます。この部分は動的リンクと呼ばれます。

メソッドのエクスポート

メソッドの実行が開始された後、メソッドを終了するには、次の 2 つの方法しかありません。

  • メソッドによって返されたバイトコード命令が検出されました
  • 例外が発生しました

最初の方法は、実行エンジンが任意のメソッドによって返される任意のバイトコード命令に遭遇することです. このとき、戻り値は上位レベルのメソッド呼び出し元に渡されます (現在のメソッドを呼び出すメソッドは、呼び出し元または呼び出し元メソッドと呼ばれます)。 , メソッドの戻り値の有無と戻り値の型は、どのメソッドの return 命令に遭遇したかによって決まります. このようにメソッドを終了する方法を「通常のメソッド呼び出し完了」と呼びます.

終了する別の方法は、メソッドの実行中に例外が発生し、この例外がメソッド本体で適切に処理されていないことです。Java 仮想マシン内で発生した例外であっても、コード内で athrow bytecode 命令を使用して発生した例外であっても、このメソッドの例外テーブルに一致する例外ハンドラが見つからない限り、メソッドは終了します。このメソッドを「メソッド呼び出しの急激な完了(Abrupt Method Invocation Completion)」と呼びます。

例外完了出口で終了するメソッドは、上位レベルの呼び出し元に戻り値を提供しません。どんな exit メソッドが使用されても, メソッドが終了した後, プログラムが実行を続ける前に元のメソッドが呼び出された位置に戻る必要があります. メソッドが戻るとき, スタックフレームにいくつかの情報を保存する必要があるかもしれません.上位レベルの復元を助ける main 呼び出しメソッドの実行ステータス。

ヒープ

Java ヒープは、すべてのスレッドで共有されるメモリ領域であり、仮想マシンの起動時に作成されます。このメモリ領域の唯一の目的は、オブジェクト インスタンスを格納することであり、Java の
世界の「ほぼ」すべてのオブジェクト インスタンスと配列がここにメモリを割り当てます。

メモリ割り当ての観点から、複数のスレッド プライベート割り当てバッファー(Thread Local Allocation Buffer、TLAB)をすべてのスレッドで共有される Java ヒープに分割して、オブジェクト割り当ての効率を向上させることができます。しかし、どの角度から見ても、どのように分割しても、Javaヒープの格納内容の共通性は変わらず、どの領域であっても、オブジェクトインスタンスしか格納できない.Javaヒープを細分化する目的は、メモリのリサイクルを改善するか、メモリをより速く割り当てます。

メソッドエリア

メソッド領域 (メソッド領域) は、Java ヒープと同様に、各スレッドが共有するメモリ領域であり、型情報、定数、静的変数、およびインスタント コンパイラによってコンパイルされ、インスタント コンパイラによって読み込まれたコード キャッシュなどのデータを格納するために使用されます。仮想マシン。

Java ヒープと同様に、連続メモリを必要とせず、固定サイズまたは拡張可能を選択できます. ガベージ コレクションを実装しないことも選択できます. この領域でのメモリ回復の目標は、主に定数プールの回復と定数プールのアンロードです。一般的に言えば、このエリアの回復効果は比較的満足できるものではなく、特にタイプの荷降ろしは非常に厳しい条件ですが、この部分の回復が必要な場合があり、この部分の回復が不完全な場合は、メモリリークにつながります。

メソッド領域、永続世代、メタスペースの関係

この3つをくっつけている理由は、ここが混同しやすいからで、Hotspot仮想マシンの場合、JDK6とJDK7ではメソッド領域が( 永続世代)、 JDK8ではPermGenメソッド領域が (メタスペース)になっています。 Metaspace?

メソッド領域は JVM の仕様であり、すべての仮想マシンはそれに従う必要があります。一般的な JVM 仮想マシン Hotspot、JRockit (Oracle)、J9 (IBM)

PermGen 空間は、JVM 仕様に基づいた HotSpot 仮想マシンのメソッド領域の実装であり、HotSpot だけが PermGen 空間を持っています。JRockit (Oracle) や J9 (IBM) などの仮想マシンにはメソッド領域がありますが、PermGen 領域はありません。

PermGen 空間は JDK7 以前であり、JDK8 で削除されたメソッド領域の HotSpot 仮想マシンのランディング実装です。

Metaspace (メタスペース) は、JDK8 以降での HotSpot 仮想マシンのメソッド領域の新しい実装です。

パーマネント ジェネレーションとメタスペースを使用して、寿命の長いオブジェクトをヒープに格納できます。メタスペースと永続世代の最大の違いは、メタスペースが仮想マシン内になく、ローカル メモリを使用することです。

クラス情報

各クラスには、コンパイル中に生成され、同じ名前の .class ファイルに保存される Class オブジェクトがあります。これらのクラス オブジェクトには、この型の親クラス、インターフェイス、コンストラクタ、メソッド、および属性などの詳細情報が含まれます。これらのクラス ファイルは、プログラムの実行時に ClassLoader によって JVM にロードされ、クラスとして表されます。 JVM が使用する Class オブジェクトは、このクラスのすべての通常のオブジェクトを作成し、このオブジェクトの情報は、メソッド領域のクラス情報に格納されます。

コンスタントプール

ランタイム定数プールは、メソッド領域の一部です。Class ファイルには、クラスのバージョン、フィールド、メソッド、インターフェイスなどの記述情報の他に、コンパイル時に生成されるさまざまなリテラルやシンボル参照を格納するための定数プール テーブル (Constant Pool Table) もあります。クラスがロードされた後、コンテンツはメソッド領域のランタイム定数プールに格納されますランタイム定数プールはメソッド領域の一部であるため、当然メソッド領域のメモリによって制限され、定数プールがメモリに適用できなくなると OutOfMemoryError 例外がスローされます。

静的変数領域

静的変数はクラス変数とも呼ばれ、クラスのすべてのインスタンスが共有されます. この領域は、静的変数と静的ブロックを格納するための専用領域です.

静的に変更されたものは、JVM の実行時にメモリにロードされるため、インスタンス クラスは必要ありません。

static変数はクラスローディングの準備段階でメモリを確保したりクラス変数の初期値を設定したりする. 概念的にはこれらの変数が使用するメモリはメソッド領域に確保する必要がある. ただし, メソッド領域自体は論理値であることに注意する. JDK 7 以前では、HotSpot がパーマネント生成を使用してメソッド領域を実装する場合、実装はこの論理概念に完全に準拠しますが、JDK 8 以降では、クラス変数は Class オブジェクトとともに Java ヒープに格納されます。ところで、「クラス変数はメソッド領域にある」というのは完全に論理的な概念の表現であり、この部分については、4.3.1 節で著者が紹介し、検証しました。

プログラムカウンター

プログラム カウンター (プログラム カウンター レジスタ) は小さなメモリ空間であり、現在のスレッドによって実行されるバイトコードの行番号インジケーターと見なすことができます。Java 仮想マシンの概念モデルでは、バイトコード インタープリターが動作するときにカウンターの値を変更することで、次に実行するバイトコード命令を選択します.これは、プログラムの制御フロー、分岐、ループ、ジャンプ、例外処理の指標です。 、スレッド回復、およびその他の基本的な機能は、完了するためにこのカウンターに依存する必要があります。

Java 仮想マシンのマルチスレッド化は、スレッドを順番に切り替えてプロセッサの実行時間を割り当てることで実現されるため、プロセッサ (マルチコア プロセッサのコア) は、スレッド内で常に 1 つの命令しか実行しません。そのため、スレッド切り替え後に正しい実行位置に戻るためには、スレッドごとに独立したプログラムカウンタが必要であり、スレッド間のカウンタは互いに影響を与えず、独立して格納されるようなメモリ領域を「スレッドプライベート」と呼んでいます。メモリの。

ネイティブ メソッド スタック

Native Method Stacks (Native Method Stacks) are very similar to virtual machine stacks. 違いは、仮想マシン スタックが Java メソッド (つまり、バイトコード) を実行するために仮想マシンにサービスを提供するのに対し、ネイティブ メソッド スタックは仮想マシンにサービスを提供することです。マシンが使用するローカル (ネイティブ) メソッド サービス。

説明: この記事は長さが限られているため、インタビューの内容の一部のみが表示されます. 完全な Java インタビュー学習ドキュメントがコンパイルされています. 友達が必要な場合は、Java と Dachang のインタビュー学習を受け取るために私にプライベート メッセージ (必要) を送信してください.素材無料!

 

 

おすすめ

転載: blog.csdn.net/m0_67788957/article/details/123737648