この記事では主に、Java メモリ割り当てのスタック、ヒープ、定数プールを分析することにより、その動作原理を詳細に説明します。
1. Java仮想マシンのメモリプロトタイプ
レジスタ: プログラムを制御することはできません。 スタック: 基本的なデータとオブジェクト参照を格納しますが、オブジェクト自体はスタックに格納されず、ヒープに格納されます。 new で生成されたデータ。 : 静的メンバー定数プールはオブジェクトに格納されます。非 RAM ストレージ: ハードディスクなどの永続的なストレージ領域です。
2. 定数プール
定数プールとは、コンパイル時に決定され、コンパイルされたメモリに保存される定数プールを指します。クラスファイル内の一部のデータ。コード内で定義されたさまざまな基本型 (int、long など) とオブジェクト型 (String や配列など) を含む定数値 (final) に加えて、テキスト形式のいくつかのシンボリック参照も含まれます。 、 のような:
1. クラスとインターフェイスの完全修飾名。
2. フィールド名と記述子。
3. メソッド、名前、および記述子。
仮想マシンは、ロードされたタイプごとに一定のプールを維持する必要があります。定数プールは、この型で使用される定数の順序付きセットで、直接定数 (文字列、整数、浮動小数点定数) や他の型、フィールド、メソッドへのシンボリック参照が含まれます。文字列定数の場合、その値は定数プールにあります。 JVM の定数プールは、メモリ内にテーブルの形式で存在します。String 型の場合、リテラル文字列値を格納するために使用される固定長の CONSTANT_String_info テーブルがあります。 注: このテーブルには、シンボルではなくリテラル文字列値のみが格納されます。 。ただし、定数プール内の文字列値の格納場所を明確に理解する必要があります。プログラムが実行されると、定数プールはヒープではなくメソッド領域に格納されます。
3. Java メモリ割り当てにおけるスタック
スタックの基本単位はフレーム (またはスタック フレーム) です。Java スレッドが実行されるたびに、Java 仮想マシンは Java スタックをスレッドに割り当てます。スレッドが特定の Java メソッドを実行すると、フレームが Java スタックにプッシュされ、このフレームはパラメータ、ローカル変数、オペランド、中間演算結果などを格納するために使用されます。このメソッドの実行が完了すると、フレームがスタックからポップされます。 Java スタック上のすべてのデータはプライベートであり、他のスレッドはそのスレッドのスタック データにアクセスできません。関数内で定義された変数データおよびオブジェクト参照変数のいくつかの基本的なタイプは、関数のスタック メモリに割り当てられます。変数がコードのブロック内で定義されると、Java はスタック上の変数にメモリ領域を割り当てます。変数がスコープから出ると、Java は変数に割り当てられたメモリ領域を自動的に解放し、そのメモリ領域をすぐに使用できるようになります。他の目的に使用すること。
4. Java メモリ割り当てにおけるヒープ
Java 仮想マシンのヒープは、new によって作成されたオブジェクトと配列を格納するために使用されます。 ヒープに割り当てられたメモリは、Java 仮想マシンの自動ガベージ コレクション メカニズムによって管理されます。簡単に言うと、スタックと比較すると、ヒープは主に Java オブジェクトの保存に使用され、スタックは主にオブジェクト参照の保存に使用されます。ヒープに配列またはオブジェクトが生成された後、特殊な変数も使用できます。スタック内で定義されているため、スタック内のこの変数の値は、ヒープ メモリ内の配列またはオブジェクトの最初のアドレスと等しくなります。スタック内のこの変数は、配列またはオブジェクトの参照変数になります。 参照変数は、配列またはオブジェクトに名前を付けることと同じであり、スタック内の参照変数を使用して、プログラム内のヒープ内の配列またはオブジェクトにアクセスできます。参照変数は、配列またはオブジェクトに名前を付けることと同じです。
参照変数は、定義時にスタック上に割り当てられる通常の変数です。プログラムがスコープ外で実行されると解放されます。配列やオブジェクト自体はヒープ内に割り当てられます。配列やオブジェクトを生成するために new を使用するステートメントが配置されているコード ブロックの外でプログラムが実行された場合でも、配列やオブジェクト自体が占有しているメモリは解放されません。オブジェクトにそれらを指す参照変数がない場合、それはガベージとなり使用できなくなりますが、依然としてメモリ空間を占有しており、後で不定の時点でガベージ コレクターによって収集 (解放) されます。これは、Java がより多くのメモリを使用する理由でもあります。実際、スタック内の変数はヒープ メモリ内の変数を指します。これは Java のポインタです。
Java のヒープは、クラス オブジェクトがスペースを割り当てるランタイム データ領域です。これらのオブジェクトは、new、newaray、anewarray、multianewarray などの命令によって作成され、プログラム コードを明示的に解放する必要はありません。ヒープはガベージ コレクションによって収集されます。ヒープの利点は、メモリ サイズを動的に割り当てることができることです。ヒープは実行時に動的にメモリを割り当て、Java のガベージ コレクタがこれらの不要になったメモリを自動的に収集するため、事前にライフタイムをコンパイラに伝える必要がありません。ただし、実行時に動的にメモリを割り当てる必要があるため、アクセス速度が遅いという欠点があります。
スタックの利点は、アクセス速度がヒープに比べてレジスタに次いで速いことと、スタックのデータを共有できることです。ただし、スタックに保存されるデータのサイズと有効期間を決定する必要があり、柔軟性に欠けるという欠点があります。スタックには主に、いくつかの基本的なタイプの変数データ (int、short、long、byte、float、double、boolean、char) とオブジェクト ハンドル (参照) が格納されます。
スタックの非常に重要な特殊機能は、スタックに格納されたデータを共有できることです。次のようにも定義するとします。
int a=3; int b=3; コンパイラはまず int a = 3 を処理し、スタック上に値 3 があるかどうかを確認します。 3 を設定し、それを格納し、a を 3 にポイントします。次に、b の参照変数を作成した後、処理 int b = 3; スタック上に値 3 がすでに存在するため、b は直接 3 をポイントします。このように、aとbが同時に現れ、どちらも3の場合を指します。
このとき、a=4 が再度設定されると、コンパイラはスタックに 4 の値があるかどうかを再検索し、ない場合は 4 を格納し、すでに存在する場合は 4 をポイントします。は、このアドレスを直接指します。したがって、a の値が変化しても b の値には影響しません。
この種のデータ共有は、同時に 1 つのオブジェクトを指す 2 つのオブジェクトの参照の共有とは異なることに注意してください。この場合、a の変更は b には影響せず、コンパイラによって完了されるためです。スペースの節約に役立ちます。オブジェクト参照変数がオブジェクトの内部状態を変更すると、別のオブジェクト参照変数に影響を与えます。