1概要
Javaは、プラットフォーム不可知論者、セキュリティ、およびネットワークモビリティをサポートすることが知られています。 Javaプラットフォームは、Java Virtual MachinesとJava Coreクラスで構成されており、より低いオペレーティングシステムに関係なく、純粋なJavaプログラム用の統一されたプログラミングインターフェイスを提供します。まさに、その主張が「一度コンパイルされ、どこでも実行される」と主張するのは、まさにJava仮想マシンのためです。
1.1 Javaプログラム実行プロセス
Javaプログラムの実行は、コンピレーション環境と実行環境に依存します。ソースコードは実行可能なマシンコードに変換され、次のプロセスで完了します。
Javaテクノロジーのコアは、すべてのJavaプログラムが仮想マシンで実行されるため、Java Virtual Machineです。 Javaプログラムの操作には、Java Virtual Machine、Java API、Javaクラスファイルの協力が必要です。 Java Virtual Machineインスタンスは、Javaプログラムの実行を担当しています。 Javaプログラムが開始されると、仮想マシンインスタンスが生まれます。プログラムが終了すると、仮想マシンインスタンスが死にます。
Javaのクロスプラットフォーム機能は、さまざまなプラットフォームをターゲットとする仮想マシンがあるためです。
1.2 Java仮想マシン
Java仮想マシンの主なタスクは、クラスファイルをロードしてその中のバイトコードを実行することです。下の図からわかるように、Java仮想マシンにはクラスローダーが含まれており、プログラムやAPIからクラスファイルをロードできます。プログラムの実行に必要なクラスのみがJava APIにロードされ、バイトコードは実行エンジンによって実行されます。
Java仮想マシンがホストオペレーティングシステム上のソフトウェアによって実装されると、Javaプログラムはローカルメソッドを呼び出すことでホストと対話します。 JavaメソッドはJava言語で記述され、Bytecodeにコンパイルされ、クラスファイルに保存されます。ローカルメソッドは、動的リンクライブラリに保存されたプロセッサ関連のマシンコードにコンパイルされたC/C ++/アセンブリ言語で記述され、形式は各プラットフォームに独自のものです。したがって、ローカルな方法は、Javaプログラムを基礎となるホストオペレーティングシステムに接続することです。
Java仮想マシンは、クラスファイルがどのように作成されたか、そしてそれが改ざんされたかどうかを知らないため、クラスファイルで定義されているタイプを安全に使用できるようにクラスファイル検出器を実装します。クラスファイルチェッカーは、4つの独立したスキャンを通じてプログラムの堅牢性を保証します。
classクラスファイルのコレクション
・タイプデータのセマンティックチェック
bytecode検証
・シンボル参照検証
ByteCodeを実行すると、Java仮想マシンは、他の組み込みのセキュリティメカニズムも実行します。これらは、Javaプログラムの堅牢性をJavaプログラミング言語として保証する特性であり、Java仮想マシンの特性でもあります。
type-Safe参照変換
・構造化されたメモリアクセス
・自動ごみ収集
・配列境界チェック
・空の引用チェック
1.3 Java仮想マシンデータタイプ
Java仮想マシンは、特定のデータ型を介して計算を実行します。データ型は、下の図に示すように、2つのタイプに分類できます。
しかし、Booleanは少し特別です。コンパイラがJavaソースコードをByteCodeにコンパイルすると、INTまたはBYTEを使用してブール値を表します。 Java仮想マシンでは、Falseは0で表され、Trueはすべての非ゼロ整数で表されます。 Java言語と同様に、Java仮想マシンの基本タイプの値範囲はどこでも一貫しています。ホストプラットフォームが何であれ、長いは常に64ビットの2つの補数を任意の仮想マシンに補完する署名整った整数です。
ReturnAddressの場合、この基本タイプは、Javaプログラムで最終句を実装するために使用されます。 Javaプログラマーはこのタイプを使用することはできず、その値は仮想マシン命令のオペコードを指します。
2アーチテクチャ
Java仮想マシンの仕様では、仮想マシンインスタンスの動作は、サブシステム、メモリ領域、データタイプ、および命令の観点から説明されており、これらのコンポーネントは一緒に抽象的な仮想マシン内部アーキテクチャを示しています。
2.1Classファイル
Javaclassファイルには、クラスまたはインターフェイスに関するすべての情報が含まれています。クラスファイルの「ベースタイプ」は次のとおりです。
| U1 | 1バイト、符号なしタイプ |
| U2 | 2バイト、符号なしタイプ |
| U4 | 4バイト、符号なしタイプ |
| U8 | 8バイト、符号なしタイプ |
もっと知りたい場合は、OracleのJVMSE7に公式仕様を提供します:Java®仮想マシン仕様
クラスファイルの内容:
classfile {u4 Magic; //マジック番号:0xcafebabe、それがJavaクラスファイルu2 minor_versionであるかどうかを判断するために使用されます。 //マイナーバージョン番号u2 major_version; //メインバージョン番号u2 constry_pool_count; //定数プールサイズcp_info curntion_pool [curntion_pool_count-1]; //定数プールU2 Access_Flags; //クラスおよびインターフェイスレベルでフラグにアクセスします(|操作を通じて取得)u2 this_class; //クラスインデックス(定数プールのクラス定数を指す)U2 Super_Class; //現在のクラスインデックス(定数プールのクラス定数を指す)U2 Interfaces_Count; //インターフェイスインデックスカウンターU2インターフェイス[interfaces_count]; //インターフェイスインデックスセットu2 fields_count; //フィールドカウントカウンターfield_info fields [fields_count]; //フィールドテーブルセットu2 methods_count; //メソッドカウントカウンターmethod_infoメソッド[Methods_count]; //メソッドテーブルSET U2 ATTRIBUTES_COUNT; //属性属性の数属Attributes [astributes_count]; //属性テーブル} 2.2クラスローダーサブシステム
クラスローダーサブシステムは、タイプ情報を見つけて読み込む責任があります。実際、Java仮想マシンには、システムローダーとユーザー定義のローダーの2種類のローダーがあります。前者はJava仮想マシンの実装の一部であり、後者はJavaプログラムの一部です。
bootstrapClassloader:Javaのコアライブラリをロードするために使用され、ネイティブコードに実装されており、java.lang.classloaderから継承されていません。
∎拡張機能クラスローダー:Java Extensionライブラリをロードするために使用されます。 Java Virtual Machineの実装は、拡張ライブラリディレクトリを提供します。このクラスローダーは、このディレクトリにJavaクラスを探してロードします。
Applicationアプリケーションクラスローダー:Javaアプリケーション(ClassPath)のClassPathに従ってJavaクラスをロードします。一般的に、Javaアプリケーションクラスはそれによってロードされます。 ClassLoader.getSystemClassLoader()を介して取得できます。
システムが提供するクラスローダーに加えて、開発者はjava.lang.classloaderクラスを継承して特別なニーズを満たすことにより、独自のクラスローダーを実装できます。
クラスローダーサブシステムには、Java仮想マシンの他のいくつかのコンポーネントとJava.Langライブラリのクラスが含まれます。 ClassLoaderによって定義されたメソッドは、クラスローダーメカニズムにアクセスするためのプログラムのインターフェイスを提供します。さらに、ロードされた各タイプについて、Java仮想マシンは、java.lang.classクラスのインスタンスを作成してタイプを表します。他のオブジェクトと同様に、ユーザー定義のクラスローダーとクラスのインスタンスはメモリのヒープ領域に配置され、ロードされたタイプ情報はメソッド領域にあります。
バイナリクラスファイルの検索とインポートに加えて、クラスローダーサブシステムは、インポートされたクラスの正しさの確認、クラス変数のメモリの割り当てと初期化、およびシンボリック参照の解析についても責任を負う必要があります。これらのアクションも次の順序で実行する必要があります。
・ロード(タイプのバイナリデータを検索してロード)
・接続(実行検証:インポートされたタイプの正しさを確認します。準備:クラス変数にメモリを割り当て、デフォルト値に初期化します。解析:タイプのシンボリック参照を直接参照に変換します)
・初期化(クラス変数は正しい初期値に初期化されます)
2.3メソッド領域
Java仮想マシンでは、ロードされたタイプに関する情報がメソッド領域のメモリに保存されます。仮想マシンが特定のタイプをロードすると、クラスローダーを使用して対応するクラスファイルを見つけてから、クラスファイルを読み取り、仮想マシンに転送します。次に、仮想マシンはそのタイプ情報を抽出し、メソッド領域にこの情報を保存します。仮想マシンはユーザー定義のクラスローダーを介してJavaプログラムを動的に拡張できるため、メソッド領域はガベージコレクターによって収集することもできます。
次の情報はメソッド領域に保存されます。
ofこのタイプの完全に資格のある名前(完全資格のある名前Java.lang.Objectなど)
・このタイプの直接スーパークラスの完全に資格のある名前
・このタイプのクラスタイプまたはインターフェイスタイプです
cassionこのタイプのアクセス修飾子(パブリック、アブストラクト、ファイナルのサブセット)
direct Direct HyperInterfaceの完全に適格な名前のソートされたリスト
このタイプの定数プール(直接定数を含む順序付けられたコレクション[String、Integer、およびFloatingPoint定数]および他のタイプ、フィールド、および方法への象徴的な参照)
・フィールド情報(フィールド名、タイプ、修飾子)
・メソッド情報(メソッド名、返品タイプ、パラメーターの数、タイプ、修飾子)
constants定数を除くすべてのクラス(静的)変数
classクラスローダークラスへの参照(各タイプがロードされている場合、仮想マシンは、スタートアップクラスローダーまたはユーザー定義のクラスローダーによってロードされるかどうかを追跡する必要があります)
classクラスクラスへの参照(ロードされた各タイプについて、仮想マシンはそれに応じてjava.lang.classクラスのインスタンスを作成します。たとえば、java.lang.integerクラスのオブジェクトへの参照がある場合、getclass()メソッドを整数オブジェクトによって参照されるメソッドを呼び出す必要があります。
2.4ヒープ
実行時にJavaプログラムによって作成されたすべてのクラスインスタンスまたは配列(配列は、Java仮想マシンの実際のオブジェクトです)は、同じヒープに配置されます。 Java仮想マシンインスタンスには1つのヒープスペースしかないため、すべてのスレッドはこのヒープを共有します。 Java仮想マシンには、ヒープにオブジェクトを割り当てる命令があるが、仮想マシンがこのタスクを処理のためにガベージコレクターに引き渡したため、メモリを解放する命令がないことに注意する必要があります。 Java仮想マシンの仕様は、ゴミコレクターを強制するのではなく、仮想マシンの実装が「何らかの方法で」独自のヒープスペースを管理する必要があることのみを必要とします。たとえば、実装には固定サイズのヒープスペースのみがあります。スペースが充填されると、ゴミオブジェクトのリサイクルの問題を考慮していないが、仕様に準拠していることを考慮しているOutFmemoryの例外を単にスローするだけです。
Java仮想マシン仕様は、Javaオブジェクトがヒープ内でどのように表現されるかを指定していません。これにより、設計方法に関する仮想マシンの決定を実装します。可能なヒープ設計は次のとおりです。
ハンドルプール、オブジェクトプール。オブジェクトの参照は、ハンドルプールへのローカルポインターです。このデザインの利点は、ヒープフラグメントの並べ替えを助長します。オブジェクトプール内のオブジェクトを移動する場合、ハンドルパーツはオブジェクトを指すポインターの新しいアドレスを変更するだけです。欠点は、オブジェクトのインスタンス変数にアクセスするたびに、2つのポインターを通過する必要があることです。
2.5 Javaスタック
スレッドが開始されるたびに、Java仮想マシンはJavaスタックを割り当てます。 Javaスタックは多くのスタックフレームで構成され、1つのスタックフレームにはJavaメソッド呼び出しの状態が含まれています。スレッドがJavaメソッドを呼び出すと、仮想マシンは新しいスタックフレームをスレッドのJavaスタックに押し込みます。メソッドが戻ると、スタックフレームがJavaスタックからポップアップします。 Javaスタックは、ローカル変数、パラメーター、返品値、操作の中間結果などを含むスレッドにJavaメソッド呼び出しのステータスを保存します。Java仮想マシンにはレジスタがなく、その命令セットにはJavaスタックを使用して中間データを保存します。この設計の理由は、Java仮想マシンの命令セットを可能な限りコンパクトに保持し、一般的なレジスタがほとんどないプラットフォームでJava仮想マシンの実装を容易にするためです。さらに、スタックベースのアーキテクチャは、ランタイム中に特定の仮想マシンによって実装された動的コンパイラとインスタントコンパイラのコードを最適化するのにも役立ちます。
2.5.1スタックフレーム
スタックフレームは、ローカル変数領域、オペランドスタック、フレームデータ領域で構成されています。仮想マシンがJavaメソッドを呼び出すと、対応するクラスのタイプ情報からこのメソッドのローカル変数領域とオペランドスタックサイズを取得し、これに従ってスタックフレームメモリを割り当ててからJavaスタックに押し込みます。
2.5.1.1ローカル変数領域
ローカル変数領域は、単語の長さの単位で0からカウントされる配列に編成されます。 ByteCode命令は、タイプの値、Float、Reference、およびReturndressの値から始まるインデックスを介してITのデータを使用し、Arrayの1つのアイテムを占有しますが、BYTE、SHORT、およびCHARの値は、配列に保存される前にINT値に変換され、1つのアイテムも占有されます。しかし、タイプの値と2倍の値は、配列内の2つの連続した項を占めています。
2.5.1.2オペランドスタック
ローカル変数領域と同様に、オペランドスタックは単語の長さの配列にも編成されます。標準のスタック操作スタックを介してアクセスし、スタックアウトします。プログラムカウンターにプログラムの命令で直接アクセスできないため、Java仮想マシンの命令はオペランドスタックからオペランドを取得するため、その動作はレジスタではなくスタックに基づいています。 Virtual Machineは、ほとんどの命令がここからデータをポップアップし、操作を実行し、結果をオペランドスタックに戻す必要があるため、オペランドスタックをワークスペースとして取得します。
2.5.1.3フレームデータ領域
ローカル変数領域とオペランドスタックに加えて、Javaスタックフレームには、一定のプール分析、通常のメソッドリターン、および例外ディスパッチメカニズムをサポートするためのフレームデータ領域も必要です。仮想マシンが一定のプールデータを必要とする命令を実行したいときはいつでも、フレームデータ領域の定数プールへのポインターを介してアクセスします。一定のプールの解析に加えて、フレームデータ領域は、仮想マシンがJavaメソッドの通常の端または異常な中絶を処理するのにも役立ちます。リターンが正常に終了する場合、仮想マシンは、コールを開始するメソッドのスタックフレームを復元する必要があります。これには、プログラムカウンターがコールメソッドを開始する次の命令を指すように設定することが含まれます。メソッドに戻り値がある場合、仮想マシンはコールを開始するメソッドのオペランドスタックに押し込む必要があります。 Javaメソッドの実行中に例外終了を処理するために、フレームデータ領域には、このメソッドの例外表への参照も保持します。
2.6プログラムカウンター
実行中のJavaプログラムの場合、各スレッドにはプログラムカウンターがあります。プログラムカウンターは、PCレジスタとも呼ばれます。プログラムカウンターは、ローカルポインターとリターンドレスの両方を保持できます。スレッドがJavaメソッドを実行する場合、プログラムカウンターの値は常に次の実行された命令のアドレスです。ここのアドレスは、メソッド開始命令に対するメソッドバイトコードのローカルポインターまたはオフセットである可能性があります。スレッドがローカルメソッドを実行している場合、プログラムカウンターの値は「未定義」です。
2.7ローカルメソッドスタック
ローカルメソッドインターフェイスは、何らかのローカルメソッドスタックを使用します。スレッドがJavaメソッドを呼び出すと、仮想マシンは新しいスタックフレームを作成し、Javaスタックに押し込みます。ローカルメソッドを呼び出すと、仮想マシンはJavaスタックを変更せずに保持し、スレッド付きJavaスタックの新しいスタックに押し込まれなくなります。仮想マシンは、単に動的に接続し、指定されたローカルメソッドを直接呼び出します。
メソッド領域とヒープは、仮想マシンインスタンスのすべてのスレッドによって共有されます。仮想マシンがクラスファイルをロードすると、クラスファイルに含まれるバイナリデータからタイプ情報を解析し、メソッド領域にタイプ情報を配置します。プログラムが実行されているとき、仮想マシンは、実行時にプログラムによって作成されたすべてのオブジェクトをヒープに配置します。
他のランタイムメモリ領域と同様に、ローカルメソッドスタックで占めるメモリ領域は、必要に応じて動的に拡張または縮小することができます。
3実行エンジン
Java仮想マシン仕様では、実行エンジンの動作は命令セットを使用して定義されます。実行エンジンを実装するデザイナーは、Bytecodeの実行方法を決定します。実装を解釈したり、その場でコンパイルしたり、チップの命令またはそれらの混合物を使用して直接実行したりできます。
実行エンジンは、抽象的な仕様、具体的な実装、または実行中のインスタンスとして理解できます。抽象仕様命令セットを使用して、実行エンジンの動作を指定します。特定の実装では、ソフトウェア、ハードウェア、ツリーテクノロジーの組み合わせなど、さまざまなテクノロジーを使用する場合があります。ランタイムインスタンスとしての実行エンジンはスレッドです。
実行中のJavaプログラムの各スレッドは、独立した仮想マシン実行エンジンのインスタンスです。スレッドライフサイクルの最初から最後まで、Bytecodeを実行するか、ローカルメソッドを実行しています。
3.1命令セット
メソッドのバイトコードストリームは、Java仮想マシンからの一連の命令で構成されています。各命令には、シングルバイトのオペコードが含まれ、その後0以上のオペランドが含まれています。オペコードは、実行する操作を表します。オペランドは、Java仮想マシンにオペコードの実行に必要な追加情報を提供します。仮想マシンが命令を実行すると、現在の定数プールのアイテム、現在のフレームのローカル変数の値、または現在のフレームのオペランドスタックの上部にある値を使用できます。
抽象実行エンジンは、一度に1つのバイトコード命令を実行します。 Java仮想マシンで実行されているプログラムの各スレッド(実行エンジンインスタンス)は、この操作を実行します。実行エンジンはオペコードを取得し、オペコードにオペランドがある場合、オペランドが取得されます。オペコードとフォローオペランドで指定されたアクションを実行し、次のオペコードを取得します。 Bytecodeの実行プロセスは、スレッドが完了するまで続き、スレッドの完了は、最初の方法から戻るか、スローされた例外をキャッチしないことでマークを付けることができます。
4ローカルメソッドインターフェイス
JNI(JavanativeInterface)とも呼ばれるJavaローカルインターフェイスは、携帯性のために準備されています。ローカルメソッドインターフェイスにより、ローカルメソッドは次のことを行うことができます。
データを渡すか返します
操作インスタンス変数
クラス変数を操作するか、クラスメソッドを呼び出します
オペランドアレイ
ヒープオブジェクトをロックします
新しいクラスをロードします
例外を投げます
Javaメソッドを呼び出すローカルメソッドによってスローされた例外をキャッチします
仮想マシンによってスローされた非同期例外をキャプチャします
ゴミコレクターオブジェクトが不要になったことを示します
要約します
上記は、Java Virtual Machine Architectureの詳細な理解に関するこの記事に関するすべてです。すべての人に役立つことを願っています。興味のある友人は、このサイトの他の関連トピックを引き続き参照できます。欠点がある場合は、それを指摘するためにメッセージを残してください。このサイトへのご支援をありがとうございました!