Java 開発の基礎を始めるために、Java コードはマシン上でどのように実行されるのか

コンピュータが認識できるのは、マシンコードと呼ばれる機械命令コードです。マシンコードはバイナリであり、コンピュータはそれを直接認識できますが、人間の言語とはあまりにも異なるため、人間が理解して記憶するのは簡単ではありません。その後、さまざまな高級言語が誕生し、人々は高級言語でプログラムを書き、そのプログラムを解釈したり、機械語にコンパイルしたりします。

たとえば、Python はインタープリタ型言語です。Python プログラムのソース コードをコンパイルする必要はなく、ソース コードから直接プログラムを実行できます。Python インタープリターはソース コードをバイトコードに変換し、コンパイルされたバイトコードを実行のために Python 仮想マシン (PVM) に転送します。

C 言語は典型的なコンパイル言語であり、最初にコンパイラーを使用してマシンコードにコンパイルする必要があります。たとえば、通常、C 言語プログラムをコンパイルするには gcc を使用します。

$ gcc hello.c # コンパイル

$ ./a.out # 実行

こんにちは世界!

それでは、Java はインタプリタ言語ですか、それともコンパイル言語ですか?

「Java はコンパイル言語とインタプリタ言語の両方の特性を持っています。」プログラマは Java プログラムを作成した後、javac を使用してそれを JVM が使用できるバイトコード クラス ファイルにコンパイルする必要があります。次に、JVM はクラス ファイルをロードし、それを 1 つずつ解釈して実行します。実行プロセス中に、一部のホット コードはジャストインタイム コンパイラによってマシン コードにコンパイルされます。

ソースコードからバイトコードへ

Java 言語のソース コードは、拡張子 .java が付いたファイルです。もちろん、groovy、kotlin など、他の多くの高級言語も JVM 上に構築されています。ソースコードは人々が見て、読みやすく、理解しやすく、保守しやすいものです。

ソース コードはバイトコードを取得するためにコンパイルされます。バイトコードは JVM によって使用され、理解しやすく、識別しやすいものです。バイトコードの接尾辞は「.class」で、その形式はJVMの計画の集合であり、ドキュメントに比べれば人間にはかろうじて理解できるが、Javaコードよりも理解するのが難しい。

Java は Python とは異なります。Python はバイトコード ファイルをコンパイルする必要がありません (もちろん、Python にもこの操作が用意されています)。コンパイルは自動プロセスであり、通常はその存在を気にする必要はありません。Java は最初にバイトコード ファイルをコンパイルします。これにより、JVM はバイトコード ファイルを直接読み取ることができるため、モジュールのロード時間を節約し、効率を向上させることができます。同時に、バイトコードの形式により、ソース コードを保護できるリバース エンジニアリングの難易度も高まります (もちろん、逆コンパイルすることもできます)。

JVM に詳しい友人は、JVM に「クラス ロード プロセス」があることを知っていますが、これは古い固定概念とも言え、面接官からもよく質問されます。クラスロードプロセスとは、実際には、クラスファイルの読み取りからクラスの準備、そして最終的にクラスの破棄までの JVM のプロセス全体を指します。

つまり、「クラスファイルは実際には「クラス」に基づいており、Javaファイルとは多少異なります。」Java ファイル内で複数のクラスを宣言すると、Javac でコンパイルしたときに複数のクラス ファイルが見つかります。たとえば、One.java ファイルを宣言します。

パブリッククラス One {

パブリック クラス OneInner {}

プライベート クラス OnePrivateInner {}

パブリック静的クラス OneStaticInner {}

プライベート静的クラス OneprivateStaticInner {}

}

クラス 2{}

Javac でコンパイルすると、6 つのクラス ファイルが作成されます。

➜ $ls

'1 つのKaTeX 解析エラー: 25 番目の二重上付き文字: …class' '̲1 つのOneStaticInner.class' One.class Two.class

'1 つのKaTeX 解析エラー: 25 番目の二重上付き文字: …eInner.class' '̲One OneprivateStaticInner.class' One.java

バイトコードからマシンコードへ

バイトコードをロードして使用する

前述したように、JVM はクラス ファイルをロードし、ロードされた Java クラスはメソッド領域に格納されます。指定したクラスのメインメソッドをエントリポイントとして実行を開始します。実際に実行するとき、仮想マシンはメソッド領域のコードを実行し、JVM はヒープとスタックを使用して実行時データを保存します。

メソッドが入力されるたびに、Java 仮想マシンはローカル変数とバイトコード オペランドを格納するために、現在のスレッドのスタックにスタック フレームを生成します。このスタック フレームのサイズは事前に計算されます。
IMG_256
メソッドを終了するとき、それが正常な戻りであろうと異常な戻りであろうと、Java 仮想マシンは「現在のスレッドの現在のスタック フレームをポップアップ」し、それを破棄します。

Java 仮想マシンは、マシンで実行するためにバイトコードをマシンコードに変換する必要があります。この処理には 2 つの形式があり、1 つはバイトコードを 1 つずつ機械語に変換して実行する解釈と実行、もう 1 つはメソッドに含める Just-In-Time コンパイル (JIT) です。バイトコードはマシンコードにコンパイルされてから実行されます。
IMG_257
レイヤードコンピレーション

これら 2 つのコンパイル方法はどのように連携するのでしょうか?

HotSpot 仮想マシンには、複数のジャストインタイム コンパイラ C1、C2、および Graal が含まれています。その中で、Graal は実験的なジャストインタイム コンパイラであり、パラメータ -XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler で有効にでき、C2 に代わるものです。

C1 と C2 にはそれぞれ長所と短所があり、さまざまなシナリオに適しています。Java 7 より前では、選択できるコンパイラは 1 つだけでした。C1 はコンパイルが速いですが、生成されたコードの実行効率は平均的です。実行時間が短いプログラムや起動パフォーマンスが要求されるプログラムによく使用されます。実行に時間がかかるプログラムや最高のパフォーマンスが必要なプログラムによく使用されます。サーバー側。実際、C1 に対応するパラメータはクライアント、C2 に対応するパラメータはサーバーであり、これらもアプリケーション シナリオと一致します。

Java7 では、C1 の起動パフォーマンスの利点と C2 のピークパフォーマンスの利点を組み合わせた階層化コンパイルの概念が導入されています。C1 と C2 でコンパイルされるマシンコードは異なります。C2 コードの実行効率は、C1 コードよりも 30% 以上高くなります。マシンコードが高速であればあるほど、コンパイルにかかる時間も長くなります。階層化コンパイルは、妥協的な方法であり、短時間でコンパイルされるホット度の低いコードの一部を満足させるだけでなく、ホットコードの最適化も満足させることができます。

ホットコード

では、ホットコードを決定するにはどうすればよいでしょうか?

JVM は、主に呼び出し数やループバック数などのメソッドの実行時情報を収集します。ジャストインタイム コンパイルは、「メソッド呼び出しの数とループバックの数の合計が指定されたしきい値を超えた」ときにトリガーされます。

->

ループバックの数は、メソッド内のコードのループ数として単純に理解できます。たとえば、メソッド内には for ループや while ループがあります。

<-

階層化コンパイルが登場する前は、このしきい値はパラメーター -XX:CompileThreshold で指定されており、C1 を使用する場合は 1500、C2 を使用する場合は 10000 でした。

段階的コンパイルが有効な場合、JVM は別のしきい値システムを使用します。このシステムでは、しきい値のサイズが動的に調整されます。JVM は、しきい値にいくつかの係数を乗算します。この係数は、現在コンパイルされるメソッドの数と正の相関があり、コンパイル スレッドの数と負の相関があります。

コンパイルスレッド

デフォルトでは、コンパイル スレッドの総数はプロセッサの数に応じて調整されます。Java 仮想マシンは、これらのコンパイル スレッドを C1 と C2 (それぞれ少なくとも 1 つ) に 1:2 の比率で割り当てます。たとえば、クアッドコア マシンの場合、コンパイル スレッドの総数は 3 つで、そのうち 1 つは C1 コンパイル スレッド、2 つは C2 コンパイル スレッドです。

->

マシン リソースが少なすぎる場合は、それぞれに 1 つのスレッドが存在する可能性があります。

<-

arthas を使用するとコンパイル スレッドを確認できます。
IMG_258
Arthas では
、ID が -1 であり、優先度も -1 であることがわかります。自分で作成したスレッド優先度は0~10なので、コンパイルスレッドの優先度が高くなります。

要約する

Java プログラムはマシン上でどのように実行されるかを一言で言えば、まず Java プログラマーが Java コードを作成し、次に Java コードが 1 つのクラス ファイルにコンパイルされ、複数のクラス ファイルが 1 つの jar パッケージまたは war にパッケージ化されます。パッケージ。次に、JVM はクラス ファイルをロードし、まずそれをバイトコードとして解釈して実行します。プログラムが一定期間実行された後、JVM はメソッドの呼び出しとループの回数を通じてメソッドがホット コードであるかどうかを判断し続けます。ホット コードである場合は、階層化コンパイルを使用し、コンパイル スレッドを通じてバイトコードにコンパイルします。そしてそれをマシン上で実行します。

記事の出典: ネットワークの著作権は原作者に帰属します

上記のコンテンツは営利目的ではありません。知的財産権に関する問題が含まれる場合は、編集者にご連絡ください。すぐに対処します。

おすすめ

転載: blog.csdn.net/xuezhangmen/article/details/132035953