1. Java 仮想マシンとは何ですか
Java 仮想マシンについて話すとき、次のことを意味する場合があります:
1. 抽象的な Java 仮想マシン仕様
2. 特定の Java 仮想マシン実装
3 . 実行中の Java 仮想マシン インスタンス
2. Java 仮想マシンのライフ サイクル
実行中の Java 仮想マシンには、Java プログラムを実行するという明確なタスクがあります。プログラムの実行開始時に実行され、プログラムの終了時に停止します。同じマシン上で 3 つのプログラムを実行すると、3 つの Java 仮想マシンが実行されることになります。
Java 仮想マシンは常に main() メソッドで開始されます。このメソッドは public である必要があり、void を返し、文字列配列を直接受け入れる必要があります。プログラムを実行するときは、main() メソッドを Java 仮想マシンにラップするクラス名を指定する必要があります。
Main() メソッドはプログラムの開始点であり、それが実行されるスレッドはプログラムの初期スレッドとして初期化されます。プログラム内の他のすべてのスレッドは彼によって開始されます。 Java には、デーモン スレッドと非デーモン スレッドの 2 種類のスレッドがあります。デーモンスレッドとは、Java 仮想マシン自体が使用するスレッドで、例えばガベージコレクションを担当するスレッドがデーモンスレッドです。もちろん、独自のプログラムをデーモンスレッドとして設定することもできます。 Main() メソッドを含む初期スレッドはデーモン スレッドではありません。
Java 仮想マシン内で通常のスレッドが実行されている限り、Java 仮想マシンは停止しません。十分な権限がある場合は、exit() メソッドを呼び出してプログラムを終了できます。
3. Java 仮想マシンのアーキテクチャ
Java 仮想マシンの仕様では、一連のサブシステム、メモリ領域、データ型、および使用ガイドラインが定義されています。これらのコンポーネントは Java 仮想マシンの内部構造を構成し、Java 仮想マシンの実装に明確な内部構造を提供するだけでなく、Java 仮想マシン実装の外部動作を厳密に規制します。
各 Java 仮想マシンにはクラス ローダー サブシステム (クラス ローダー サブシステム) があり、プログラムに型 (クラスとインターフェイス) をロードし、それらに一意の名前を付ける役割を果たします。各 Java 仮想マシンには、ロードされたクラスに含まれる命令の実行を担当する実行エンジン (実行エンジン) があります。
プログラムの実行には、バイトコード、ロードされたクラスのその他の追加情報、プログラム内のオブジェクト、メソッドのパラメータ、戻り値、ローカル変数、処理された中間変数など、一定量のメモリ空間が必要です。 Java 仮想マシンは、これらすべての情報をデータ領域に保存します。すべての Java 仮想マシン実装にはデータ領域が含まれていますが、Java 仮想マシン仕様のデータ領域に関する規定は非常に抽象的です。構造の詳細の多くは Java 仮想マシンの実装者に任されています。さまざまな Java 仮想マシン実装のメモリ構造は大きく異なります。実装によっては大量のメモリを使用する場合もあれば、メモリをほとんど使用しない実装もあります。実装によっては仮想メモリを使用する場合もあれば、使用しない場合もあります。この比較的洗練された Java 仮想マシンのメモリ仕様により、Java 仮想マシンを幅広いプラットフォームに実装できます。
データ領域の一部はプログラム全体で共有され、他の部分は別のスレッドによって制御されます。各 Java 仮想マシンには、プログラム全体で共有されるメソッド領域とヒープが含まれています。 Java 仮想マシンはクラスをロードして解析した後、クラス ファイルから解析した情報をメソッド領域に保存します。プログラムの実行時に作成されたオブジェクトはヒープに格納されます。
スレッドが作成されると、スレッドには独自の PC レジスタ「pc レジスタ」(プログラム カウンタ) と Java スタック (Java スタック) が割り当てられます。スレッドがネイティブ メソッドを使用しない場合、PC レジスタにはスレッドによって実行される次の命令が格納されます。 Java スタックは、スレッドがメソッドを呼び出すときに、ローカル変数、呼び出しメソッドのパラメーター、戻り値、処理された中間変数などの状態を保存します。ネイティブ メソッドが呼び出されたときの状態は、ネイティブ メソッド スタック、場合によってはレジスタまたはその他のプラットフォームに依存しないメモリに保存されます。
Java スタックはスタック フレーム (またはフレーム) で構成されます。スタック ブロックには、Java メソッド呼び出しのステータスが含まれます。スレッドがメソッドを呼び出すと、Java 仮想マシンは新しいブロックを Java スタックにプッシュし、メソッドが終了すると、Java 仮想マシンは対応するブロックをポップして破棄します。
Java 仮想マシンは、計算の中間結果を保存するためにレジスタを使用しませんが、Java スタックを使用して中間結果を保存します。これにより、Java 仮想マシンの命令がよりコンパクトになり、レジスタのないデバイス上での Java 仮想マシンの実装が容易になります。
図の Java スタックでは、PC レジスタのスレッド 3 が灰色になっています。これは、ローカル メソッドが実行されており、その次の実行命令が PC レジスタに保存されていないためです。
4. データ型
Java 仮想マシンで使用されるすべてのデータは特定のデータ型を持ち、データ型と操作は Java 仮想マシンの仕様で厳密に定義されています。 Javaのデータ型は、プリミティブ型(プリミティブ型)と参照データ型(参照型)に分けられます。参照型は実際のオブジェクトに依存しますが、オブジェクト自体には依存しません。プリミティブ データ型は何にも依存せず、データ自体を表します。
Java プログラミング言語のすべてのプリミティブ データ型は、ブール型を除き、Java 仮想マシンのプリミティブ データ型です。コンパイラは、Java ソース コードを独自のコードにコンパイルするときに、整数型 (int) またはバイト型 (byte) を使用してブール型を表します。 Java 仮想マシンは、ブール値 false を表すために整数 0 を使用し、ブール値 true を表すためにゼロ以外の整数を使用します。ブール配列は、ヒープ内のバイト配列またはビット フィールドに格納される場合もありますが、バイト配列として表されます。
Boolean を除き、Java 言語の他のプリミティブ型は Java 仮想マシンのデータ型です。 Java では、データ型は整数の byte、short、int、long、char および浮動小数点型の float、double に分類されます。 Java 言語のデータ型は、どのホストでも同じスコープを持ちます。
Java 仮想マシンには、Java 言語では使用できないプリミティブ データ型の戻り値型 (returnValue) も存在します。このタイプは、Java プログラムで「finally 句」を実装するために使用されます。詳細については、第 18 章の「Finally 句」を参照してください。
参照型は、クラス型、インターフェイス型、配列型として作成できます。これらはすべて、動的に作成されたオブジェクトを参照します。参照型が null を参照する場合、オブジェクトが参照されていないことを意味します。
Java 仮想マシンの仕様では、各データ型が表す範囲のみが定義されており、格納中に各型が占めるスペースは定義されていません。これらをどのように保存するかは、Java 仮想マシンの実装者に任されています。浮動小数点型の詳細については、第 14 章「浮動小数点演算」を参照してください。
TypeRange
byte8 ビット符号付き 2 の補数整数 (-27 ~ 27 - 1、両端を含む)
short16 ビット符号付き 2 の補数整数 (-215 ~ 215 - 1、両端を含む)
int32 ビット符号付き 2 の補数整数 (-231 ~ 231 - 1、両端を含む)
long64 ビット符号付き 2 の補数整数 (-263 ~ 263 - 1、両端を含む)
char16 ビット符号なし Unicode 文字 (0 ~ 216 - 1、包括的)
float32 ビット IEEE 754 単精度 float
double64 ビット IEEE 754 倍精度 float
returnValue 同じメソッド内のオペコードのアドレス
referenceヒープ上のオブジェクトへの参照、または null
5. バイト長
Java 仮想マシン内の最小のデータ単位ワード (ワード) で、そのサイズは Java 仮想マシンの実装者によって定義されます。ただし、1 ワードのサイズは、byte、short、int、char、float、returnValue、reference を収容するのに十分である必要があり、2 ワードは、long、double を収容するのに十分である必要があります。したがって、仮想マシンの実装者は少なくとも 31 ビットより小さいワードを提供する必要がありますが、特定のプラットフォームで最も効率的なワード長を選択することが最善です。
Java プログラムは実行時に、実行中のマシンの語長を判断できません。ワード長はプログラムの動作には影響せず、Java 仮想マシンでの表現方法の 1 つにすぎません。
6. クラス ローダー サブシステム
Java 仮想マシンには、基本クラス ローダーとクラス ローダー オブジェクトという 2 種類のクラス ローダーがあります。元のクラス ローダーは Java 仮想マシン実装の一部であり、クラス ローダー オブジェクトは実行中のプログラムの一部です。異なるクラスローダーによってロードされたクラスは、異なる名前空間によって分離されます。
クラス ローダーは、Java 仮想マシンの他の多くの部分と、java.lang パッケージ内の多くのクラスを呼び出します。たとえば、クラス ロード オブジェクトは java.lang.ClassLoader サブクラスのインスタンスです。ClassLoader クラスのメソッドは、仮想マシンのクラス ロード メカニズムにアクセスできます。Java 仮想マシンによってロードされる各クラスは、Java として表現されます。 .lang.Class クラスのインスタンス。クラスローダーオブジェクトやクラスオブジェクトも他のオブジェクトと同様にヒープに格納され、ロードされた情報はメソッド領域に格納されます。
1. ロード、リンク、および初期化
クラス ロード サブシステムは、クラス ファイルの検索とロードを担当するだけでなく、次の厳密な手順に従って他の多くのことも実行します: (具体的な情報は「クラスのライフ サイクル」を参照) 7章の)
1)、読み込み: 指定された型(クラスとインターフェース)のバイナリ情報を検索してインポート
2)、接続: 検証、準備、解析
①検証:インポートされた型
② 準備: 型にメモリを割り当て、デフォルト値に初期化します
③ 解析: 文字参照を解析して直接取り込みます
3)、初期化: Java コードを呼び出し、初期化します クラス変数は適切な値
2. プリモーディアル クラス ローダー
各 Java 仮想マシンは、クラス ファイル形式に準拠し、信頼できる種類のものをロードできるプリミティブ クラス ローダーを実装する必要があります。ただし、Java 仮想マシンの仕様ではクラスのロード方法が定義されていないため、Java 仮想マシンの実装者が決定します。指定されたタイプ名を持つタイプの場合、元のローダーはタイプ名に「.class」を加えたファイルを検索し、それを仮想マシンにロードする必要があります。
3. クラス ローダー オブジェクト
クラス ローダー オブジェクトは Java プログラムの一部ですが、ClassLoader クラスの 3 つのメソッドは Java 仮想マシンのクラス ローディング サブシステムにアクセスできます。
1)、保護された最終クラスdefineClass(...): このメソッドを使用してバイト配列にアクセスし、新しい型を定義します。
2), protected Class findSystemClass(文字列名): 指定されたクラスをロードし、ロードされていればそのままリターンします。
3)、protected Final voidsolveClass(Class c):defineClass() メソッドはクラスをロードするだけであり、このメソッドはその後の動的接続と初期化を担当します。
具体的な情報については、第 8 章「リンク モデル」を参照してください。
4. 名前空間
複数のクラスローダーが同じクラスをロードする場合、名前の一意性を保証するために、クラスをロードするクラスローダーの識別子をクラス名の前に追加する必要があります。詳細については、第 8 章「リンク モデル」を参照してください。
7. メソッド領域
Java 仮想マシンでは、ロードされた型の情報はメソッド領域に格納されます。メモリ内のこの情報の編成形式は、仮想マシンの実装者によって定義されます。たとえば、仮想マシンが「リトル エンディアン」プロセッサで動作する場合、情報を「リトル エンディアン」形式で保存できます。 Java クラス ファイルでは、「ビッグ エンディアン」形式で保存されます。設計者は、ローカル マシンに最適な表現形式でデータを保存し、プログラムを最速で実行できるようにすることができます。ただし、メモリの量が少ないデバイスでは、仮想マシンの実装者が大量のメモリを占有することはありません。
プログラム内のすべてのスレッドはメソッド領域を共有するため、メソッド領域情報にアクセスする方法はスレッドセーフである必要があります。 Lava というクラスをロードするスレッドが 2 つある場合、このクラスをロードできるのは 1 つのスレッドだけであり、もう 1 つは待機する必要があります。
プログラム実行中のメソッド領域のサイズは可変であり、プログラム実行中にプログラムを拡張することができます。一部の Java 仮想マシン実装では、パラメーターを通じてメソッド領域の初期サイズ、最小値、最大値をカスタマイズすることもできます。
メソッド領域もガベージ コレクションできます。プログラム内のコンテンツはクラス ローダーによって動的にロードされるため、すべてのクラスが参照されなくなる可能性があります。クラスがこの状態になると、ガベージ コレクションが行われる可能性があります。ロードされていないクラスには 2 つの状態があり、1 つは実際にはロードされておらず、もう 1 つは「参照されていない」状態です。詳細については、第 7 章の「クラスの存続期間」を参照してください。
1. 型情報 (型情報)
ロードされた各型は、次の情報を Java 仮想マシンのメソッド領域に保存します:
1)、型の完全名 (完全修飾名)型)
2)、型の直接スーパークラスの完全修飾名 (親型がない場合、または Frey 形式が java.lang.Object でない場合) (型の直接スーパークラスの完全修飾名)
3)、型がクラスかインターフェイスか (型がクラスであるかどうか)
4)、型修飾子 (public、private、protected、static、final、volatile、transient など) (型の修飾子)
5)、直接スーパーインターフェイスの完全修飾名の順序付きリスト)
型の完全名によって保存されるデータ構造は、仮想マシンの実装者によって定義されます。さらに、Java 仮想マシンは、各タイプの次の情報も格納します:
1)、そのタイプの定数プール (そのタイプの定数プール)
2)、およびタイプ フィールド情報 (フィールド情報)
3)、型メソッド情報 (メソッド情報)
4)、すべての静的クラス変数 (非 const) 情報 (定数を除く、型で宣言されたすべてのクラス (静的) 変数)
5)、クラス ClassLoader
6) への参照、クラス Class
1)、型の定数プール (型の定数プール)
定数プールに保存されるすべての型は、文字列、整数、整数などの直接定数 (リテラル) を含む、順序付けられた定数のセットです。浮動小数点数、ドット定数、および型、フィールド、メソッドへのシンボリック参照。定数プールに保存される各定数には、配列内のフィールドと同様にインデックスがあります。定数プールには、定数プール内のすべての型で使用される型、フィールド、およびメソッドへの文字参照が格納されるため、動的接続の主要オブジェクトでもあります。詳細は、第 6 章「Java クラス ファイル」を参照してください。
2)、タイプ フィールド情報 (フィールド情報)
フィールド名、フィールド タイプ、フィールド修飾子 (public、private、protected、static、final、volatile、transient など)、クラスの順序で定義されたフィールド。
3)、型メソッド情報 (メソッド情報)
メソッド名、メソッド戻り値の型 (または void)、メソッドパラメータの数、型とその順序、フィールド修飾子 (public、private、protected、static、final) 、揮発性、一時的など)、メソッドがクラス内で定義される順序
抽象的かつローカルでない場合、このメソッドはメソッドのバイトコードとオペランド スタックのサイズも保存する必要があります。メソッドとローカル変数領域のサイズ (詳細は後述)、例外リスト (詳細は第 17 章「例外」を参照)
4)、クラス (静的) 変数 (クラス変数)
クラス変数はクラスのすべてのインスタンスによって共有されるため、クラスのインスタンスを経由しなくてもアクセスできます。これらの変数は (クラスのインスタンスではなく) クラスにバインドされているため、クラスの論理データの一部です。 Java 仮想マシンがこのクラスを使用する前に、クラス変数 (non-final) にメモリを割り当てる必要があります。
定数 (final) の処理方法は、このクラス変数 (non-final) とは異なります。各タイプが定数を使用する場合、それを独自の定数プールにコピーします。定数も、定数プールに格納される点を除き、クラス変数と同様にメソッド領域に格納されます。 (おそらく、クラス変数はすべてのインスタンスで共有されますが、定数プールは各インスタンスに固有です)。非最終クラス変数は、それを宣言する型のデータの一部として保存されますが、最終定数は、それを使用する型のデータの一部として保存されます。詳細については、第 6 章「Java クラス ファイルJava クラス ファイル」を参照してください。
5)、クラス ClassLoader への参照
Java 仮想マシンによってロードされるすべてのタイプは、仮想マシンによって保存される必要があります。このタイプがロードされるかどうかは、仮想マシンによって保存される必要があります。元のクラスローダーまたはクラスローダー。クラスローダーによってロードされた型は、クラスローダーへの参照を維持する必要があります。この情報は、クラスローダーが動的に接続するときに使用されます。クラスが別のクラスを参照する場合、仮想マシンは参照される型が同じクラス ローダーによってロードされていることを保存する必要があり、これは仮想マシンが異なる名前空間を維持するプロセスでもあります。詳細については、第 8 章「リンク モデル」を参照してください。
6)、クラス Class への参照 (クラス Class への参照)
Java 仮想マシンは、ロードされた各クラスに対して java.lang.Class クラスのインスタンスを作成します。タイプ。 。また、Class クラス メソッド
public static Class forName(String className) を使用して、クラスを検索またはロードし、対応する Class クラスのインスタンスを取得することもできます。 Class クラスのこのインスタンスを通じて、Java 仮想マシンのメソッド領域の情報にアクセスできます。詳細については、Class クラスの JavaDoc を参照してください。
2. メソッド テーブル
メソッド領域に格納されているすべてのデータにより効率的にアクセスするには、これらのデータのストレージ構造を慎重に設計する必要があります。すべてのメソッド領域には、上記の元の情報が保存されることに加えて、メソッド リストなどのアクセスを高速化するためのデータ構造も用意されています。ロードされた非抽象クラスごとに、Java 仮想マシンはメソッド リストを生成します。このリストには、このクラスによって呼び出されるすべてのインスタンス メソッドへの参照が保存され、親クラスで呼び出されるメソッドにエラーが報告されます。詳細については、第 8 章「リンク モデル」を参照してください。 8. ヒープ
Java プログラムがクラスのインスタンスまたは配列を作成すると、ヒープ内の新しいオブジェクトにメモリが割り当てられます。仮想マシンにはヒープが 1 つだけあり、すべてのスレッドがそれを共有します。
1. ガベージ コレクション
ガベージ コレクションは、参照されていないオブジェクトを解放するための主な方法です。また、ヒープの断片化を減らすためにオブジェクトを移動することもあります。ガベージ コレクションは Java 仮想マシンの仕様では厳密に定義されていませんが、Java 仮想マシンの実装は何らかの方法で独自のヒープを管理する必要があると定義されています。詳細については、第 9 章「ガベージ コレクション」を参照してください。
2. オブジェクト ストレージ構造 (オブジェクト表現)
Java 仮想マシンの仕様では、オブジェクトがヒープにどのように格納されるかは定義されていません。各オブジェクトは主に、そのクラスと親クラスで定義されたオブジェクト変数を格納します。特定のオブジェクトを参照する場合、仮想マシンはこのオブジェクトのデータを迅速に見つける必要があります。さらに、メソッド領域でのオブジェクトの参照など、オブジェクトを通じてメソッド オブジェクト データを参照するためのメソッドを提供する必要があるため、オブジェクトによって保存されるデータにはメソッド領域への何らかの形式のポインタが含まれることがよくあります。
考えられるヒープ設計の 1 つは、ヒープを参照プールとオブジェクト プールの 2 つの部分に分割することです。オブジェクトへの参照は、参照プールへのローカル ポインタです。参照プール内の各エントリには、オブジェクト プール内のオブジェクト データへのポインタと、メソッド領域内のオブジェクト クラス データへのポインタの 2 つの部分が含まれています。この設計により、Java 仮想マシン ヒープの最適化が容易になります。仮想マシンがオブジェクト プール内のオブジェクトを移動するときは、対応する参照プール内のポインタ アドレスを変更するだけで済みます。ただし、オブジェクトのデータにアクセスするたびに、ポインターを 2 回処理する必要があります。以下の図は、このヒープ設計を示しています。第 9 章「ガベージ コレクション」の HeapOfFish アプレットは、この設計を示しています。
もう 1 つのヒープ設計は次のとおりです。オブジェクトの参照は、データの山を指し、対応するオブジェクトを指すオフセット ポインターです。この設計によりオブジェクトへのアクセスは容易になりますが、オブジェクトの移動は非常に複雑になります。次の図は、この設計を示しています。
プログラムがオブジェクトを別の型に変換しようとするとき、仮想マシンは、変換がオブジェクトの型であるかその親の型であるかを判断する必要があります。プログラムがinstanceofステートメントを使用する場合も同様のことが行われます。プログラムがオブジェクトのメソッドを呼び出すとき、仮想マシンは動的バインディングを実行する必要があり、呼び出すメソッドのタイプを決定する必要があります。この場合も上記の判断が必要となります。
仮想マシンの実装者がどの設計を使用するかに関係なく、各オブジェクトのメソッド リストと同様の情報を保存できます。オブジェクト メソッドの呼び出し速度が向上するため、仮想マシンのパフォーマンスを向上させることは非常に重要ですが、仮想マシンの仕様には、同様のデータ構造を実装する必要があるという要件はありません。以下の図は、この構造を示しています。この図は、オブジェクト参照に関連付けられたすべてのデータ構造 (
1)、型データへのポインター
2)、およびオブジェクトのメソッド リストを示しています。メソッド リストは、呼び出すことができるオブジェクトのすべてのメソッドへのポインタの配列です。メソッド データには、オペコード スタックのサイズとメソッド スタックのローカル変数領域、メソッドのバイトコード、および例外リストの 3 つの部分が含まれます。
Java 仮想マシン内の各オブジェクトは、複数のスレッドを同期するために使用されるロック (ミューテックス) に関連付ける必要があります。同時に、このオブジェクトのロックを保持できるのは 1 つのオブジェクトだけです。このオブジェクトのロックを所有している人は、ロックを複数回申請できますが、実際にオブジェクトのロックを解放するには、対応する回数だけロックを解放する必要があります。多くのオブジェクトは存続期間を通じてロックされないため、この情報は必要な場合にのみ追加する必要があります。多くの Java 仮想マシン実装では、オブジェクトのデータに「ロック データ」が含まれておらず、必要な場合にのみ対応するデータが生成されます。オブジェクト ロックの実装に加えて、各オブジェクトは「待機セット」実装にも論理的に関連付けられます。ロックは、スレッドが他のスレッドに干渉することなく共有データを独立して処理するのに役立ちます。 「待機セット」は、グループ スレッドが協力して同じ目標を達成するのに役立ちます。 「待機セット」は、多くの場合、Object クラスの wait() および Notice() メソッドを通じて実装されます。
ガベージ コレクションには、ヒープ内のオブジェクトが関連付けられているかどうかに関する情報も必要です。 Java 仮想マシンの仕様では、ガベージ コレクションではオブジェクトのファイナライザ メソッドが一度実行されますが、ファイナライザ メソッドによるオブジェクトの再参照は許可されており、オブジェクトが再度参照されない場合は、ファイナライズ メソッドを再度呼び出す必要はありません。したがって、仮想マシンは、finalize メソッドが実行されたかどうかに関する情報も保存する必要があります。詳細については、第 9 章の「ガベージ コレクション」を参照してください。
3. 配列の保存 (配列表現)
Java では、配列は完全なオブジェクトです。オブジェクトと同様にヒープに保存されます。 Class クラスのインスタンスへの参照。同じ次元と型のすべての配列は同じクラスを持ち、配列の長さは考慮されません。クラスに対応する名前は次元と型で表現されます。例えば、整数データのクラス名は「[I」、バイト型三次元配列のクラス名は「[[[B」、二次元オブジェクトデータのクラス名は「[[ Ljava.lang.Object」。
多次元配列は、以下に示すように、配列の配列として表されます。
配列は、配列の長さ、配列のデータ、およびオブジェクト配列型データへの参照をヒープに格納する必要があります。仮想マシンは、配列参照を通じて配列の長さを取得し、インデックス付けを通じて特定のデータにアクセスし、Object によって定義されたメソッドを呼び出すことができる必要があります。オブジェクトは、すべてのデータ クラスの直接の親クラスです。詳細は、第 6 章「クラス ファイル」を参照してください。
9. PC レジスタ (プログラム カウンター) (プログラム カウンター)
プログラム カウンターは、各スレッドが実行を開始するときに作成されます。プログラム カウンタの長さは 1 ワードのみなので、ローカル ポインタと returnValue を保持できます。スレッドが実行されると、プログラム カウンタには実行中の命令のアドレスが格納されます。このアドレスはローカル ポインタまたはメソッド バイトコードから始まるオフセット ポインタです。ネイティブメソッドが実行される場合、プログラムカウンタ値は定義されません。
10. Java スタック
スレッドが開始されると、Java 仮想マシンはそのスレッド用の Java スタックを作成します。 Java スタックは、いくつかの個別のフレーム クラスを使用してスレッドのステータスを記録します。 Java 仮想マシン ヒープ Java スタックには、フレームのプッシュとポップという 2 つの操作しかありません。
スレッド内で実行されているメソッドをカレントメソッドと呼び、カレントメソッドに対応するフレームをカレントフレームと呼びます。現在のメソッドを定義するクラスを現在のクラスと呼び、現在のクラスの定数プールを現在の定数プールと呼びます。スレッドが実行されると、Java 仮想マシンは現在のクラスと現在の定数プールを追跡します。ただし、スレッドがフレームに格納されているデータを操作する場合、スレッドは現在のフレームのデータのみを操作します。
スレッドがメソッドを呼び出すと、仮想マシンは新しいフレームを生成し、それをスレッドの Java スタックにプッシュします。この新しいフレームが現在のフレームになります。メソッドが実行されると、現在のフレームを使用してメソッドのパラメーター、ローカル変数、中間構造、およびその他のデータが保存されます。メソッドには、正常終了と異常終了の 2 つの終了方法があります。どちらの方法でメソッドを起動しても、Java 仮想マシンがポップアップしてメソッドのフレームを破棄し、前のメソッドのフレームが現在のフレームになります。
フレームに保存されたすべてのデータは、そのフレームを所有するスレッドによってのみアクセスできます。スレッドは、他のスレッドのスタック内のデータにはアクセスできません。したがって、メソッドのローカル変数にアクセスする場合、マルチスレッド同期を考慮する必要はありません。
Java スタックは、メソッド領域やヒープと同様に、連続したメモリ空間を必要とせず、分散メモリ空間またはヒープ上に格納できます。スタックの特定のデータと長さは、Java 仮想マシンの実装者によって定義されます。実装によっては、スタックの最大化と最小化を実行するメソッドが提供される場合があります。
11. スタック フレーム
スタック フレームには、ローカル変数、オペランド スタック、およびフレーム データの 3 つの部分が含まれています。ローカル変数とオペランド スタックのサイズはワード単位で測定され、コンパイル中に決定されます。フレーム データのサイズは実装によって異なります。プログラムがメソッドを呼び出すと、仮想マシンはクラス データからローカル変数とオペランド スタックのサイズを取得し、適切なサイズとフレームを作成して、それを Java スタックにプッシュします。
1. ローカル変数
ローカル変数は Java スタック フレーム内で 0 から数えられる配列として編成されており、命令はそのインデックスを指定することでローカル変数領域から対応する値を取得します。 int、float、reference、returnValue は 1 ワードを占め、byte、short、char は int に変換されて格納され、long と doublel は 2 ワードを占めます。
ディレクティブは、2 つのワード インデックスのうちの最初のインデックスを指定することで、long または double の値を取得します。たとえば、long 値がインデックス 3 または 4 に格納されている場合、命令は 3 を使用して long 値を取得できます。
ローカル変数領域には、メソッド パラメーターとローカル変数が含まれます。コンパイラは、メソッド パラメータを宣言された順序で配列の先頭に配置します。ただし、コンパイラは、ローカル変数配列内でローカル変数を任意に配置することができ、たとえば、ループ変数 i,j のように、2 つのローカル変数が重複しない 2 つの領域にある場合など、2 つのローカル変数が共通のアドレスを共有することもできます。
仮想マシンの実装者は、ローカル変数領域のデータを記述するために任意の構造体を使用できますが、仮想マシンの仕様では、long および doublel の格納方法は定義されていません。
2. オペランドスタック (Operand Stack)
オペランドスタックもローカル変数と同様にワード単位の配列に構成されます。ただし、ローカル変数のようなインデックスを介してアクセスされるのではなく、プッシュ値とポップ値を介してアクセスされます。 1 つの命令がスタックに値をプッシュすると、次の命令はその値をポップして使用できます。
プログラムカウンタとは異なり、オペランドスタックには命令から直接アクセスすることはできませんが、命令はオペランドスタックに直接アクセスできます。 Java 仮想マシンは、その命令が同じレジスタからではなくスタックからオペランドを取得するため、レジスタベースではなくスタックベースです。もちろん、命令は、命令の後のオペコードや定数プールなど、他の場所からオペランドを取得することもできます。ただし、Java 仮想マシン命令は主に、必要なオペランドをオペランド スタックから取得します。
Java 仮想マシンはオペランド スタックを作業領域として扱います。多くの命令は、最初にオペランド スタックから値をポップし、処理後に結果をオペランド スタックに戻します。 add 命令の実行プロセスを次の図に示します。まず、iload_0 命令と iload_1 命令を実行して、加算する必要がある 2 つの数値をローカル メソッド領域から取得し、オペランド スタックにプッシュします。次に、iadd 命令を実行し、 now Pop 2 つの値を出力して加算し、結果をオペランド スタックにプッシュし、最後に istore_2 命令を実行して結果をポップアウトし、ローカル メソッド領域に代入します。
3. フレームデータ
Java スタック フレームには、ローカル変数やオペランド スタックの処理に加え、定数プール、メソッドの戻り値、例外の分散をサポートするために必要なデータも含まれており、フレーム データ中間に保存されます。
仮想マシンが定数プールへの参照を使用する命令に遭遇すると、フレーム データ内の定数領域へのポインタを介して必要な情報にアクセスします。前述したように、定数領域の参照は最初はシンボリック参照です。仮想マシンがこれらの参照をチェックする場合でも、それらは文字参照です。したがって、仮想マシンはこの時点でこの参照を変換する必要があります。
メソッドが正常に戻ると、仮想マシンはこのメソッドを呼び出したメソッドのスタック フレームを再構築する必要があります。実行されたメソッドに戻り値がある場合、仮想マシンはこの値を呼び出し側メソッドのオペランド スタックにプッシュする必要があります。
フレーム データには、仮想マシンが例外を処理するために使用する例外テーブルへの参照も含まれています。例外テーブルは、catch ステートメントによって保護されるバイトコードのセクションを定義します。例外テーブル内の各個別には、保護する必要があるバイトコードの範囲と、例外がキャッチされたときに実行する必要があるバイトコードの場所も含まれています。メソッドが例外をスローすると、Java 仮想マシンは例外テーブルを使用して例外の処理方法を決定します。仮想マシンは一致する catch を見つけた場合、制御を catch ステートメントに移します。一致するキャッチが見つからない場合、メソッドは異常終了し、呼び出したメソッドで処理を続行します。
上記の 3 つの用途に加えて、フレーム データにはデバッグ情報などの実装に依存するデータも含まれる場合があります。
12. ローカル メソッド スタック
ローカル メソッド領域は、仮想マシンのさまざまな実装に依存します。仮想マシンの実装者は、ネイティブ メソッドの実行にどのメカニズムを使用するかを決定できます。
すべてのネイティブ メソッド インターフェイスは、何らかの形式のネイティブ メソッド スタックを使用します。
13. 実行エンジン
Java 仮想マシン実装の中核は実行エンジンです。 Java 仮想マシン仕様では、実行エンジンは一連の命令として記述されています。仕様では、ディレクティブごとに何を行うべきかが説明されていますが、その方法は説明されていません。
1. 命令セット
Java 仮想マシンでは、メソッドのバイトコード ストリームは一連の命令です。各命令は、バイト オペレーション コード (Opcode) と可能なオペランド (Operand) で構成されます。オペコードは何をすべきかを指示し、オペランドはオペコードの実行に必要な追加情報を提供します。抽象実行エンジンは、一度に 1 つの命令を実行します。このプロセスは、実行のすべてのスレッドで発生します。
場合によっては、実行エンジンがローカル メソッドの呼び出しを必要とする命令に遭遇することがあります。この場合、実行エンジンはローカル メソッドの呼び出しを試みますが、ローカル メソッドが返されると、実行エンジンはバイトコードの実行を継続します。ストリーム、次の命令。ネイティブ メソッドは、Java 仮想マシンの命令セットの拡張とみなすこともできます。
次にどの命令を実行するかを決定することも、実行エンジンの作業の一部です。実行エンジンには、次の命令を取得するための 3 つの方法があります。ほとんどの命令は、それに続く命令を実行します。goto や return などの一部の命令は、実行時に次の命令を決定します。命令が例外をスローすると、実行エンジンは catch ステートメントと照合して次の命令を決定します。処刑される。
プラットフォームの独立性、ネットワーク モビリティ、およびセキュリティは、Java 仮想マシン命令セットの設計に影響を与えます。プラットフォームの独立性は、命令セット設計における主な影響要因の 1 つです。スタックベースの構造により、Java 仮想マシンをより多くのプラットフォームに実装できます。オペコードが小さくなり、構造がコンパクトになるため、バイトコードはネットワーク帯域幅をより効率的に利用できるようになります。ワンタイムバイトコード検証により、パフォーマンスに大きな影響を与えることなくバイトコードの安全性が高まります。
2. 実行テクノロジー
Java 仮想マシンの実装では、インタープリター実行、ジャストインタイム コンパイル、ホットスポット コンパイル、シリコンでのネイティブ実行など、多くの実行テクノロジーを使用できます。
3. スレッド
Java 仮想マシンの仕様では、より多くのプラットフォームで実装するためのスレッド モデルが定義されています。 Java スレッド モデルの 1 つの目標は、ネイティブ スレッドを活用することです。ローカル スレッドを使用すると、Java プログラム内のスレッドをマルチプロセッサ マシン上で同時に実行できます。
Java スレッド モデルのコストの 1 つはスレッド優先度であり、Java スレッドは 1 ~ 10 の優先度レベルで実行できます。 1 が最低、10 が最高です。設計者がネイティブ スレッドを使用していれば、これら 10 個の優先順位をローカルの優先順位にマッピングした可能性があります。 Java 仮想マシンの仕様では、優先度の高いスレッドが CPU 時間を取得でき、優先度の高いスレッドがすべてブロックされている場合は優先度の低いスレッドも CPU 時間を取得できるとだけ定義されていますが、保証はありません。優先度が低いスレッドは CPU 時間を取得できません。高優先度のスレッドがブロックされていない場合の一定量の CPU 時間。したがって、異なるスレッド間で連携する必要がある場合は、「同期(synchronizatoin)」を使用する必要があります。
同期とは、オブジェクトのロックとスレッドの待機とアクティブ化 (スレッドの待機と通知) の 2 つの部分を意味します。オブジェクト ロックは、スレッドが他のスレッドからの干渉を受けないようにするのに役立ちます。スレッドの待機とアクティブ化により、異なるスレッドが連携できるようになります。
Java 仮想マシンの仕様では、Java スレッドは変数、メイン メモリ、ワーキング メモリとして記述されます。 Java 仮想マシンの各インスタンスにはメイン メモリがあり、そこにはオブジェクト、配列、クラス変数などのすべてのプログラム変数が含まれます。各スレッドには独自の作業メモリがあり、使用する可能性のある変数のコピーを保持します。ルール:
1)、変数の値をメインメモリからワーキングメモリにコピーします。
2)、ワーキングメモリの値をメインメモリに書き込みます。
変数が同期されていない場合、スレッドはメイン メモリ内の変数を任意の順序で更新できます。マルチスレッド プログラムを正しく実行するには、同期メカニズムを使用する必要があります。
14. ネイティブ メソッド インターフェイス
Java 仮想マシンの実装では、ネイティブ メソッド インターフェイスを実装する必要はありません。実装によっては、ネイティブ メソッド インターフェイスをまったくサポートしていない場合があります。 Sun のネイティブ メソッド インターフェイスは JNI (Java Native Interface) です。
15. 実機
16. 数学的手法: シミュレーション (永遠の数学: シミュレーション)
以上がJava 仮想マシン (JVM) の知識ポイントの紹介の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。