
Java仮想マシン(JVM)は、コンピュータ上でJavaプログラムだけでなく、Javaバイトコードにコンパイルされた他の言語(JVM言語と呼ばれる)で記述されたプログラムも実行できるようにする仮想マシンです。JVMは、JVMの実装に必要な要件を正式に記述した仕様によって詳細に規定されています。仕様があることで、異なる実装間でのJavaプログラムの相互運用性が確保され、Java開発キット(JDK)を使用するプログラム開発者は、基盤となるハードウェアプラットフォームの特異性を気にする必要がなくなります。
JVMのリファレンス実装は、 OpenJDKプロジェクトによってオープンソースコードとして開発されており、 HotSpotと呼ばれるJITコンパイラが含まれています。Oracleから提供されている商用サポート対象のJavaリリースは、OpenJDKランタイムをベースとしています。Eclipse OpenJ9も、OpenJDK用のオープンソースJVMです。
Java仮想マシンは、仕様によって定義された抽象的な(仮想)コンピュータです。これはJavaランタイム環境の一部です。使用されるガベージコレクションアルゴリズムや、Java仮想マシン命令の内部最適化(マシンコードへの変換)は規定されていません。この省略の主な理由は、実装者を不必要に制約しないためです。Javaアプリケーションは、Java仮想マシンの抽象的な仕様の具体的な実装内でのみ実行できます。[ 4 ]
Java Platform, Standard Edition (J2SE) 5.0以降、JVM仕様の変更はJavaコミュニティプロセスの下でJSR 924として開発されてきた。 [ 5 ] 2006年現在クラスファイル形式(JSR 202)[ 6 ]に提案された変更をサポートするための仕様変更は、JSR 924のメンテナンスリリースとして実施されています。JVMの仕様はブルーブック[ 7 ]として公開されており、その序文には次のように記載されています。
本仕様書は、互換性のあるクリーンルーム実装を可能にするために、Java仮想マシンを十分に文書化することを意図しています。Oracleは、Java仮想マシンの実装が正しく動作することを確認するためのテストを提供します。
最も一般的に使用されているJava仮想マシンは、OracleのHotSpotです。
OracleはJavaの商標を所有しており、実装スイートがOracleの仕様に完全に準拠していることを証明するために、その使用を許可する場合があります。
Java OpenJDKs仮想マシン(JVM)で利用可能なガベージコレクタには、以下のものがあります。
Java の設計思想は、ガベージ コレクタの存在を前提としています。C ++やRustなどの言語とは異なり、deleteキーワード (C++ のように) による決定論的なメモリ管理はできません。所有権がないため、そのような機能を導入することもできません。ただし、sun.misc.Unsafeを使用したり、java.lang.foreignを使用してJava ヒープ外のメモリを割り当てたり、解放したりすることは可能です。 [ 9 ] [ 10 ] Java は主にヒープベースの割り当てを使用するため、オブジェクトは参照として格納され、削除するとダングリング ポインタが発生します。
Foo a = new Foo (); Foo b = a ;aを削除します。System.out.println ( b ) ;JVM は、Java 仮想マシン仕様で指定されている特定の種類のデータに対して動作します。データ型は[ 11 ]プリミティブ型 (整数値と浮動小数点値) と参照型に分類できます。longおよびdouble型は64 ビットでネイティブにサポートされていますが、各ユニットが 32 ビットであるため、フレームのローカル変数またはオペランドスタックで 2 ユニットのストレージを消費します。boolean、byte、short、char型はすべて符号拡張(ゼロ拡張されcharているを除く) されており、 型と同様に 32 ビット整数として動作します。より小さい型には、ロード、ストア、および型変換のための型固有の命令がいくつかしかありません。は 8 ビット値として動作し、0 は、1 は を表します。 ( Java仮想マシン仕様第2版でこの問題が明確化されて以来、は型として扱われていますが、コンパイルおよび実行されたコードでは、メソッドシグネチャの名前マングリングとブール配列の型を除いて、とにほとんど違いはありません。メソッドシグネチャ内のはとしてマングリングされ、はとしてマングリングされます。ブール配列は型を保持しますが、要素ごとに8ビットを使用し、JVMにはブール値をビット配列にパックする組み込み機能がないため、型を除いて、配列と同じように動作し、機能します。その他のすべての用途では、ブール値を操作するすべての命令がにも使用されるため、型はJVMにとって実質的に不明です。)intbooleanbytefalsetruebooleanbooleanbytebooleanZbyteBboolean[]bytebooleanbyte
JVMには、オブジェクトや配列を格納するためのガベージコレクションされたヒープがあります。コード、定数、その他のクラスデータは「メソッド領域」に格納されます。メソッド領域は論理的にはヒープの一部ですが、実装によってはメソッド領域をヒープとは別に扱う場合があり、例えばガベージコレクションを行わない場合もあります。各JVMスレッドには、フレームを格納する独自のコールスタック(分かりやすくするために「Java仮想マシンスタック」と呼ばれます)もあります。メソッドが呼び出されるたびに新しいフレームが作成され、そのメソッドが終了するとフレームは破棄されます。
各フレームは「オペランドスタック」と「ローカル変数」の配列を提供します。オペランドスタックは、計算を実行するためのオペランドと、呼び出されたメソッドの戻り値を受け取るために使用されます。一方、ローカル変数はレジスタと同じ目的で使用され、メソッド引数を渡すためにも使用されます。したがって、JVMはスタックマシンとレジスタマシンの両方の機能を備えています。実際には、HotSpotは、テンプレートインタープリタが技術的にはコンパイラとして機能するため、インタープリタモードで実行されている場合でも、ネイティブスレッド/コールスタック以外のすべてのスタックを排除します。
JVMは参照とスタック/配列インデックスを使用してデータをアドレス指定します。ほとんどの物理マシンのようにバイトアドレス指定は使用しないため、 32ビットマシンや64ビットマシンといった一般的な分類には当てはまりません。ある意味では、ネイティブに格納できる最大の値のサイズが32ビットであるため、32ビットマシンに分類することもできます。これは、32ビット整数または浮動小数点値、あるいは32ビット参照です。参照は32ビットであるため、各プログラムは最大で2³²個の一意の参照に制限され、したがって最大で2³²個のオブジェクトしか作成できません。ただし、各オブジェクトは1バイト以上になる可能性があり、非常にlong大きくなる可能性もあります。可能な最大のオブジェクトは、長さ2³¹ -1の配列で、16 GiBのメモリを消費します。十分なメモリがあれば、このような配列を2³²個作成できる可能性があります。この結果、上限は一般的な64ビットのバイトアドレス指定可能なマシンにより近いものになります。 JVM の実装は、JVM が必要とする整数 (8、16、32、64 ビット) および浮動小数点 (32、64 ビット) 演算を正しく実装している限り、ネイティブに任意のビット幅を使用するプロセッサ上で動作するように設計できます。参照の実装方法 (ネイティブ ポインタ、圧縮ポインタ、または間接参照テーブル) によっては、オブジェクトの数が理論上の最大値よりも少なく制限される場合があります。64 ビット プラットフォーム上の JVM の実装は、32 ビット プラットフォーム上の実装よりもはるかに大きなアドレス空間にアクセスできるため、ヒープ サイズを大幅に大きくしたり、スレッドの最大数を増やしたりすることが可能になり、特定の種類の大規模アプリケーションで必要になります。ただし、64 ビット実装を使用すると、 32 ビット実装と比較してパフォーマンスが低下する場合があります。
JVM言語とは、有効なクラスファイルで表現できる関数を持つ言語であり、JVMでホストできる言語のことです。クラスファイルには、JVM命令(JVMバイトコード)、シンボルテーブル、およびその他の補助情報が含まれています。クラスファイル形式は、コンパイルされたクラスとインターフェースを表すために使用される、ハードウェアおよびオペレーティングシステムに依存しないバイナリ形式です。[ 12 ]
JVM 言語には、JVM に移植された古い確立された言語と、まったく新しい言語の両方があります。JRubyとJythonは、それぞれRubyとPython という古い言語の移植として最もよく知られています。Java バイトコードにコンパイルするために作成された新しいゼロから作成された言語の中では、Clojure、Groovy、Scala、Kotlin が最も人気があるかもしれません。JVM 言語の注目すべき特徴は、言語間の相互運用性です。つまり、互いに互換性があるため、たとえば Scala ライブラリを Java プログラムで使用したり、その逆も可能です。[ 13 ]
Java 7 JVM は、JVM で動的型付け言語をサポートする新機能である JSR 292: Java プラットフォームでの動的型付け言語のサポート[ 14 ]を実装しています。この機能は、Java 以外の言語をサポートするように JVM を拡張することをミッションとするDa Vinci Machineプロジェクト内で開発されています。 [ 15 ] [ 16 ]
JVMバイトコードの構成単位の一つにクラスがあります。クラスローダーの実装は、Javaクラスファイル形式に準拠するものであれば何でも認識してロードできなければなりません。クラスファイル以外のバイナリ形式を認識する実装も自由ですが、クラスファイルは必ず認識する必要があります。
クラスローダーは、以下の3つの基本的な動作を厳密な順序で実行します。
一般的に、クラスローダーにはブートストラップクラスローダー、拡張クラスローダー、システム/アプリケーションクラスローダーの3種類があります。
すべてのJava仮想マシン実装には、信頼できるクラスをロードできるブートストラップクラスローダーと、拡張クラスローダーまたはアプリケーションクラスローダーが必要です。Java仮想マシン仕様では、クラスローダーがどのようにクラスを検索するかについては規定されていません。
JVMには、以下のタスク群に関する命令が含まれています。
目的はバイナリ互換性です。各ホストオペレーティングシステムは、それぞれ独自のJVMとランタイムの実装を必要とします。これらのJVMはバイトコードを意味的に同じように解釈しますが、実際の実装は異なる場合があります。バイトコードをエミュレートするよりも複雑なのは、各ホストオペレーティングシステムにマッピングする必要のあるJavaコアAPIを、互換性を保ちつつ効率的に実装することです。
これらの命令は、共通のセットに基づいて動作します抽象化されたデータ型であって、特定の命令セットアーキテクチャのネイティブデータ型ではない。
Javaの基本的な理念は、ユーザープログラムがホストマシンをクラッシュさせたり、ホストマシン上の他の操作に不適切な干渉をしたりすることがなく、信頼できるコードに属する特定のメソッドやデータ構造を、同じJVM内で実行される信頼できないコードによるアクセスや破損から保護できるという点で、本質的に安全であるという点です。さらに、配列の末尾へのアクセスや初期化されていないポインタの使用など、データ破損や予期しない動作につながることが多い一般的なプログラマーのエラーは発生しません。Javaのいくつかの機能が組み合わさってこの安全性を実現しており、その中にはクラスモデル、ガベージコレクションされたヒープ、検証機能などが含まれます。
JVMは、実行前にすべてのバイトコードを検証します。この検証は主に次の3種類のチェックから構成されます。
これらのチェックのうち最初の2つは、主にクラスがロードされ使用可能になった際の検証ステップで行われます。3つ目は主に動的に実行され、クラスのデータ項目やメソッドが別のクラスから初めてアクセスされたときに行われます。
検証ツールは、有効なプログラムでは一部のバイトコード シーケンスのみを許可します。たとえば、ジャンプ (分岐) 命令は、同じメソッド内の命令のみを対象とすることができます。さらに、検証ツールは、任意の命令が固定スタック位置で動作することを保証し、[ 17 ] JIT コンパイラがスタック アクセスを固定レジスタ アクセスに変換できるようにします。このため、JVM がスタック アーキテクチャであることは、 JIT コンパイラを使用する場合にレジスタ ベースのアーキテクチャでのエミュレーションの速度低下を意味するものではありません。コード検証済みの JVM アーキテクチャでは、JIT コンパイラが、ターゲット アーキテクチャのレジスタに割り当てる必要がある名前付き仮想レジスタまたは仮想スタック位置を取得するかどうかは、違いがありません。実際、コード検証により、JVM は従来のスタック アーキテクチャとは異なります。従来のスタック アーキテクチャでは、JIT コンパイラによる効率的なエミュレーションはより複雑で、通常はより低速なインタプリタによって実行されます。さらに、デフォルトの JVM で使用されるインタープリタは、テンプレート インタープリタと呼ばれる特殊なタイプであり、一般的なインタープリタのようにスタックをエミュレートするのではなく、バイトコードを直接ネイティブのレジスタベースのマシン語に変換します。[ 18 ]多くの点で、HotSpot インタープリタは真のインタープリタではなく JIT コンパイラとみなすことができます。つまり、バイトコードが対象とするスタック アーキテクチャは実装では実際に使用されず、レジスタベースのアーキテクチャで実装できる中間表現の仕様にすぎません。スタック アーキテクチャが単なる仕様であり、レジスタベースの仮想マシンで実装されているもう 1 つの例は、共通言語ランタイムです。[ 19 ]
バイトコード検証器の元の仕様は、いくつかの点で不完全または不正確な自然言語を使用していました。JVMを形式システムとして規定する試みが数多く行われてきました。これにより、現在のJVM実装のセキュリティをより徹底的に分析し、潜在的なセキュリティの悪用を防ぐことができます。また、実行中のアプリケーションが安全であることが証明されれば、不要な安全チェックをスキップしてJVMを最適化することも可能になります。[ 20 ]
仮想マシンアーキテクチャでは、マシン内のコードが実行できるアクションを非常に細かく制御できます。このアーキテクチャは、コードが「意味的に」正しい、つまり、仮想マシンとは別のツールによって実現される(正式な)バイトコード検証プロセスを正常に通過したことを前提としています。これは、Javaアプレットやその他のセキュアなコードダウンロードで使用されるモデルのように、リモートソースからの信頼できないコードを安全に実行できるように設計されています。バイトコードが検証されると、ダウンロードされたコードは制限された「サンドボックス」内で実行されます。サンドボックスは、不正な動作や悪意のあるコードからユーザーを保護するように設計されています。バイトコード検証プロセスに加えて、発行者は証明書を購入してアプレットにデジタル署名を行い、安全性を証明できます。これにより、ユーザーにサンドボックスから抜け出してローカルファイルシステム、クリップボードにアクセスしたり、外部ソフトウェアを実行したり、ネットワークにアクセスしたりすることを許可できます。
バイトコード検証器の形式的証明は、JavaCard業界によって行われています(Java Cardバイトコード用組み込み検証器の形式的開発[ 21 ])。
ハードウェアアーキテクチャごとに、異なるJavaバイトコードインタープリタが必要です。コンピュータにJavaバイトコードインタープリタが搭載されていれば、あらゆるJavaバイトコードプログラムを実行でき、同じプログラムは、そのようなインタープリタを搭載したどのコンピュータでも実行できます。
Javaバイトコードをインタプリタで実行する場合、ネイティブマシン言語にコンパイルした同じプログラムの実行速度よりも常に遅くなります。この問題は、 Javaバイトコードを実行するためのジャストインタイム(JIT)コンパイラによって軽減されます。JITコンパイラは、プログラムの実行中にJavaバイトコードをネイティブマシン言語に変換します。変換された部分は、インタプリタで処理されるよりもはるかに高速に実行できます。この手法は、プログラムの中で頻繁に実行される部分に適用されます。このようにして、JITコンパイラは全体の実行時間を大幅に短縮できます。
Javaプログラミング言語とJavaバイトコードの間には、必ずしも関連性はありません。Javaで書かれたプログラムは、実際のコンピュータの機械語に直接コンパイルできますし、Java以外の言語で書かれたプログラムもJavaバイトコードにコンパイルできます。
Javaバイトコードはプラットフォームに依存せず、安全であるように設計されています。[ 22 ]一部のJVM実装にはインタプリタが含まれておらず、ジャストインタイムコンパイラのみで構成されています。[ 23 ]
Java Native Interface(またはNative Method Interface)は、Java仮想マシン(JVM)で実行されているJavaコードが、 C、 C ++ 、アセンブリなどの他の言語で記述されたネイティブアプリケーション(ハードウェアおよびオペレーティングシステムプラットフォームに固有のプログラム)やライブラリを呼び出したり、それらから呼び出されたりできるようにする、外部関数インターフェース(非Java )プログラミングフレームワークです。
Java 22では、Java Native Interfaceの後継と見なせる外部関数およびメモリAPIが導入されました。
JNIによってネイティブメソッドが有効になり、アプリケーションを完全にJavaプログラミング言語で記述できない状況(例えば、標準のJavaクラスライブラリがプラットフォーム固有の機能やプログラムライブラリをサポートしていない場合など)に対応できるようになります。
JNIフレームワークを使用すると、ネイティブメソッドはJavaコードがJavaオブジェクトを使用するのと同様の方法でJavaオブジェクトを使用できます。ネイティブメソッドはJavaオブジェクトを作成し、それらのオブジェクトを検査して使用してタスクを実行できます。また、ネイティブメソッドはJavaアプリケーションコードによって作成されたオブジェクトを検査して使用することもできます。
JNI では、C ブリッジを経由することなくアセンブリ コードに直接アクセスすることもできます。[3] アセンブリから Java アプリケーションにアクセスするのも同じ方法で可能です。
Javaプラットフォームの初期段階では、JVMはリッチWebアプリケーションを作成するためのWebテクノロジーとして販売されていました。2018年現在ほとんどのウェブブラウザやウェブブラウザをバンドルしているオペレーティングシステムにはJavaプラグインが付属しておらず、 Flash以外のプラグインをサイドロードすることも許可されていません。JavaブラウザプラグインはJDK 9で非推奨になりました。[ 24 ]
NPAPI Javaブラウザプラグインは、JVMがHTMLページに埋め込まれたいわゆるJavaアプレットを実行できるように設計されています。このプラグインがインストールされているブラウザでは、アプレットはページ上の指定された矩形領域に描画できます。プラグインにはJVMが含まれているため、JavaアプレットはJavaプログラミング言語に限定されず、JVMをターゲットとするあらゆる言語をプラグイン内で実行できます。限られたAPIセットにより、アプレットはユーザーのマイクや3Dアクセラレーションにアクセスできますが、矩形領域外のページを変更することはできません。主な競合技術であるAdobe Flash Playerも、この点では同様の動作をします。
2015年6月現在W3Techsによると、JavaアプレットとSilverlightの使用率はすべてのWebサイトでそれぞれ0.1%に低下し、Flashは10.8%に低下した。[ 25 ]
2016年5月以降、JavaPolyでは、変更されていないJavaライブラリをインポートし、JavaScriptから直接呼び出すことができます。JavaPolyを使用すると、ユーザーのコンピュータにJavaがインストールされていなくても、Webサイトは変更されていないJavaライブラリを使用できます。[ 26 ]
JavaScriptの実行速度が継続的に向上していることに加え、Webブラウザがプラグインをサポートしていないモバイルデバイスの利用が増加していることから、 JavaScriptへのトランスパイルを通じてこれらのユーザーをターゲットとする取り組みが進められています。ソースコードまたはJVMバイトコードをJavaScriptにトランスパイルすることが可能です。
JVM言語間で共通するJVMバイトコードをコンパイルすることで、言語の既存のバイトコードコンパイラを基盤として構築することが可能になります。主なJVMバイトコードからJavaScriptへのトランスパイラは、TeaVM [ 27 ]、Dragome Web SDKに含まれるコンパイラ[ 28 ]、Bck2Brwsr [ 29 ] 、およびj2js-compiler [ 30 ]です。
JVM言語からJavaScriptへの主要なトランスパイラには、 Google Web Toolkitに含まれるJava-to-JavaScriptトランスパイラ、J2CL [ 31 ]、Clojurescript(Clojure)、GrooScript(Apache Groovy)、Scala.js(Scala)などがあります[ 32 ] 。