Java仮想マシンの深い理解(バージョン3)の研究ノート - 仮想マシンのバイトコード実行エンジン

第8章仮想マシンのバイトコード実行エンジン

「仮想マシン」は、「物理マシン」の相対的な概念であり、両方のマシンがコードを実行する能力を持っている、違いは、物理マシンの実行エンジンは、プロセッサ、キャッシュ、命令セットと、オペレーティング・システム・レベルに直接組み込まれていますあなたが制限された命令セットアーキテクチャと実行エンジンの物理的な条件をカスタマイズすることはできませんので、仮想マシンの実行エンジンは、ソフトウェア自体によって実装されると、それらの命令を実行することが可能な、直接ハードウェア・セット形式でサポートされていません。

「Java仮想マシン仕様」、Java仮想マシンの実行エンジン大手出版社(ファサード)の統一された外観に、この概念モデルでは、Java仮想マシンバイトコード実行エンジンの概念モデルを開発しました。異なる仮想マシンの実装では、バイトコードの実装では、実行エンジンは、通常、そこに解釈され、コンパイルされた実行の2つのオプションはまた、両方の組み合わせであってもよいし、同時にそれは時間コンパイラのいくつかの異なるレベルを含んでいると考えられます実行エンジンの仕事一緒に。

ランタイムスタックフレーム構造

最も基本的な単位、「スタックフレーム」(スタック・フレーム)は、その背後にデータ構造を実施するための仮想マシンのメソッド呼び出しとメソッドをサポートするために使用されるような方法を実行するためのJava仮想マシン。

ローカル変数のスタックフレーム法、オペランドスタック、およびダイナミックリンクリターンアドレスおよび他の情報のための方法を格納します。開始から実行終了までの各メソッド呼び出しプロセス、プロセス相当スタックから仮想マシンがスタックをプッシュするたスタックフレーム。

スタックフレームが深い必要なオペランドスタックは、メソッドテーブル内のコード属性に計算して書き込む分析されているか、どのくらいのローカル変数テーブルであるとき、Javaプログラムのソースコードをコンパイルします。

ローカル変数テーブル

メソッドパラメータおよびメソッド内で定義されたローカル変数を格納するための変数値記憶のセット。最小単位として可変溝(可変スロット)にローカル変数テーブルの容量。

メソッドが呼び出され、Java仮想マシン変数のリストに転送プロセスパラメータ値を完了するためにローカル変数テーブルを使用して、すなわち、引数、パラメータに渡されます。実行(メソッドが静的に変更されていない)インスタンスメソッドである場合、ローカル変数テーブル変数スロットはビット0「この」にキーワードの方法で、オブジェクトの参照インスタンスを送達するための方法に関連するデフォルトのインデックスでありますこの暗黙のパラメータにアクセスします。パラメータテーブルに応じて他のパラメータは、タンク1を起動ローカル変数を占めるから、順に配置され、割り当て後のパラメータテーブルは、メソッド本体のスコープ内で定義された順序に従って、残りの変数再分配溝と変数を終了します。

可能、可変溝ローカル変数テーブルを再利用できるようにメモリ空間を節約するために、スタックフレームを消費します。

ローカル変数が定義されているが、初期値が割り当てられていない、それは完全に使用できなくなっている場合はローカル変数は、準備段階ではありません。コンパイラは、このプロンプトで出入りだけチェックをコンパイルすることができます。

オペランドスタック

max_stacks項目オペランドスタック(オペランドスタック)スタック操作が頻繁に呼び出されるが、間のコンパイル時Code属性で最大深度に書き込まれます。

実行の方法を開始するときは、オペランドスタックは、メソッドの実行中に、このメソッドを空にバイトコード命令の多様性を持っているし、オペランドスタックに抽出したコンテンツを書き込み、スタックがありますスタック操作。

仮想マシンのスタックのような異なる方法の二つの異なるスタックフレーム要素は、互いに完全に独立しています。しかし、いくつかの仮想マシンのほとんどを達成するために最適化され、スタックフレームは、2つの部分的にオーバーラップさせるように見えます。次のスタックフレーム番号の部分との重なり部分を聞かせオペランド上の表のローカル変数のスタックフレームスタック、これはいくつかのスペースを節約するだけでなく、より重要なことには、追加を必要とせずに、直接メソッド呼び出しの間にデータの一部を共有することができます渡されたパラメータをコピーします。

ダイナミックリンク

各スタックフレームがスタックフレームに関連する方法の実行時定数プールへの参照が含まれ、それは、通話中に動的ための接続支援方法(ダイナミックリンク)への参照を保持することです。

クラスファイルの定数プールはシンボリックリファレンス、パラメータとしてシンボル基準点の定数プール内のメソッドを呼び出すメソッドのバイトコード命令の多くが含まれています。相又は最初の使用は、この変換は静的解像度と呼ばれたときに部分を参照する、これらのシンボルは、直接参照クラスローダを変換しました。他の部分は、各動作中の直接参照に変換され、この部分は、動的リンクと呼ばれます。

メソッドの戻りアドレス

終了方法:

  • 通常の呼び出しは完了です:戻り値があるかもしれません呼び出し側の上部にカウンタ値PCの道を渡され、メソッド呼び出しがリターンアドレスとして使用することができ、スタックフレームは、カウンタ値を保存する可能性があります
  • 例外呼完了:それは、任意の上位呼び出し側が戻り値を提供し得ない、リターンアドレスは、例外ハンドラテーブルによって決定されるべきであり、このセクションのスタックフレーム情報は、一般的に保存されていません

方法終了:ローカル変数とオペランドスタックテーブルトップの回収方法、呼び出し側のスタックフレームをプッシュするオペランドスタックの戻り値、メソッド呼び出し命令以降の命令などをポイントにカウンタPCの調整値。

メソッド呼び出し

メソッド呼び出しを同じ方法コードが実行されていない、唯一のタスクは、メソッド内の特定の業務プロセスに対処するためには至っていないメソッドと呼ばれるメソッド呼び出しステージバージョン(メソッド呼び出しであることを)決定することです。

解決

コンパイルクラスファイルは、従来のプログラミング言語コンパイラを接続する手順が含まれていない、すべてのメソッド呼び出しは、クラスではなく、実際のランタイムメソッド・エントリ・アドレス(直接引用)メモリレイアウトに比べて、店舗内のファイルのみシンボリック参照です。
直接参照へのシンボリック参照の一部となるクラスローディングの解析段階では、これはという前提の上に成り立っ解決することができます。プログラムが実際に実行される前に判断することができるメソッド呼び出しのバージョンがあり、このバージョンでメソッドを呼び出しますランタイムは不変です。言い換えれば、モーメントが確定されたコンパイルするコンパイラを書かれたプログラムコードにターゲットを呼び出します。これらのメソッドを呼び出します(解像度)の解析と呼ばれています。

メソッドのバイトコード命令を呼び出します:

  • invokestatic:静的メソッドの呼び出し。
  • invokespecial:の<init>(メソッド、プライベートメソッドとメソッド親クラス)コンストラクタ呼び出しインスタンスの。
  • INVOKEVIRTUAL:すべての仮想メソッドを呼びかけ。
  • invokeinterface:メソッドの呼び出しインタフェースは、その後、実行時にオブジェクトが実装にインタフェースを決定します
  • invokedynamicの第一の動的解析メソッド呼び出しポイント修飾子は、実行時に参照され、本方法を実行します。

非仮想メソッド:シンボル参照を直接参照メソッドに分割することができるクラスローディング

  • staticメソッド
  • プライベートメソッド
  • 例のコンストラクタ
  • 親クラスのメソッド
  • 最終修正方法

メソッドの呼び出し:

  • (解像度)の解析:静的プロセス、それは完全にコンパイル時に決定され、クラスのロード分析フェーズが明確で直接的な参照にすべてのシンボルへの参照を含むことになる、あなたは完成を遅らせ再び実行する必要はありません。
  • ディスパッチ(発送):量に基づいて、割り当てられたケースの数に応じて、静的または動的とすることができる
    、単一および複数のディスパッチディスパッチに分けることができます。2つのディスパッチの方法の組み合わせのこれらの2つのタイプは、静的単一割り当て、複数のディスパッチ静的、動的単一割当て、動的ディスパッチ複数ディスパッチ組み合わせの四種類を構成します。
割り当て
  • 静的割り当て
    例:
public class StaticDispatch {
	static abstract class Human {
	}
	static class Man extends Human {
	}
	static class Woman extends Human {
	}

	public void sayHello(Human guy) {
		System.out.println("hello,guy!");
	}
	public void sayHello(Man guy) {
		System.out.println("hello,gentleman!");
	}
	public void sayHello(Woman guy) {
		System.out.println("hello,lady!");
	}

	public static void main(String[] args) {
		Human man = new Man();
		Human woman = new Woman();
		StaticDispatch sr = new StaticDispatch();
		sr.sayHello(man);
		sr.sayHello(woman);
	}
}
hello,guy!
hello,guy!
Human man = new Man();
  • 静的タイプ(静的型):ヒト可変マン静的タイプ、または外観の種類(見かけタイプ)
  • 実際の型(実際の型):男の人は、実際の変数の型、または実行時の型(実行時型)であります

静的な型と実際の型は、プログラムに変更されることがあります。
それ自体が静的な型が変更されない変数を使用する際に変化の静的タイプにのみ発生します。
変更の実際の型の結果は、実行時に確認することができます。

// 实际类型变化
Human human = (new Random()).nextBoolean() ? new Man() : new Woman();
// 静态类型变化
sr.sayHello((Man) human)
sr.sayHello((Woman) human)

コンパイラは、過負荷時の決意するための基礎として引数の静的な型ではなく、実際のタイプによって。

すべては、実行のディスパッチアクションのバージョンを確認するために、静的な型のメソッドに依存していると呼ばれている静的な割り当て
最も典型的なアプリケーションのパフォーマンスの静的割り当て方法はオーバーロードされます。

静的割り当ては、コンパイル時に発生し、解決したがって分類することができます。

  • 動的な割り当ての
    例:
public class DynamicDispatch {
	static abstract class Human {
		protected abstract void sayHello();
	}
	static class Man extends Human {
		@Override
		protected void sayHello() {
			System.out.println("man say hello");
		}
	}
	static class Woman extends Human {
		@Override
		protected void sayHello() {
			System.out.println("woman say hello");
		}
	}
	public static void main(String[] args) {
		Human man = new Man();
		Human woman = new Woman();
		man.sayHello();
		woman.sayHello();
		man = new Woman();
		man.sayHello();
	}
}
man say hello
woman say hello
woman say hello

Java仮想マシン命令INVOKEVIRTUALそのバージョンを実行するためのキー割り当て方法の実際の種類に応じ。解決プロセスはINVOKEVIRTUALの指示を実行しています:

  1. オペランド尖った物体を見つけるために、スタックの最初の要素の実際の型は、Cと称される
  2. メソッド記述子定数と単純な名前でタイプCで見つかった場合は、この方法は、プロセスの終了を見つけるために直接参照を返す場合は、アクセス権の確認、一貫している。java.lang.IllegalAccessErrorによって返しません例外。
  3. そうでない場合は、それぞれの親クラスC及び探索から検証プロセスの第二段階に応じて、継承を上げます。
  4. あなたは右の方法を見つけることはありません場合は、例外がスローされますjava.lang.AbstractMethodError。

言及したタイプの決定方法の動作に割り当てプロセスの実際のバージョンに基づいて行わ動的割り当て
ダイナミックディスパッチのアプリケーションを書き換えます。

単一および複数の派遣派遣

アプローチの受信器パラメータ及び方法は、方法の変数と呼びます。単一ディスパッチはターゲットメソッドの変数の選択で、ディスパッチは、複数の方法で標的複数の変数を選択します。

  • 静的割り当て:選択の方法に従って2点があります
    • メソッドの受信者の静的タイプ
    • パラメータ静的な型のメソッド
      には、Java言語が複数のディスパッチの種類に属し、静的に割り当てられています。
  • 動的な割り当て:1:00選定方法に従い、
    • 受信者の実際のタイプの方法
仮想マシンの動的な割り当て

--interface方法invokeinterfaceを実行するとき、最適化方法の基本的な一般的なタイプは、仮想テーブルと呼ばれるプロセスゾーン(仮想メソッドテーブル、仮想メソッドテーブルの確立であり、これに対応する、インターフェースメソッドテーブルが使用されますテーブルには、パフォーマンスを向上させるためにメタデータを交換するために、仮想メソッドテーブルのルックアップインデックスを使用して、)のITable言及しました。

仮想メソッドテーブルは、実際のメソッドエントリのアドレスを格納します。方法は、サブクラスでオーバーライドされていない場合は、アドレスエントリサブクラスvtableのエントリアドレスと親が同じであるのと同じ方法は、親クラスの入口点を達成します。子クラスは、このサブクラスのメソッドをオーバーライドする場合は仮想メソッドテーブルアドレスは、サブクラスを指すようにエントリアドレスの実装バージョンを置き換えられます。

仮想メソッドテーブルは、通常、クラスの負荷接続フェーズで初期化された後、私たちは一緒にも初期化され、このクラスの仮想メソッドテーブルへの変数の型、仮想マシンの初期値を用意しました。

動的型付け言語サポート

Java仮想マシンのバイトコード命令セットの数は、SunのJava仮想マシンは、これまでに出て以来初めてで、唯一のinvokedynamicの命令その新しい命令を持っていました。
この新しいディレクティブは、JDK 7プロジェクトの目的を高めることである:ラムダ式で行われ、だけでなく、JDK 8のための改善点の一つは、スムーズに技術的準備金を行うことができます動的型付け言語(動的型付け言語)のサポートを実現します。

動的型付け言語

動的型付け言語の重要な特徴は、メインプロセスのチェックのタイプは、多くの一般的に使用がされている言語の特性を満たすために、コンパイル時ではなく、実行時に行われているが含まれます:APL、Clojureの、アーラン、Groovyのは、JavaScript、Lispの、Luaのを、PHP、プロローグやPython、Rubyの、Smalltalkの、Tclの、そして上のようにします。

java.lang.invoke

主な目的は、コールが道路以外のターゲットメソッド参照を決定する前に、記号のみに依存「法ハンドル」(メソッドハンドル)として知られているダイナミックターゲティング機構の新たな方法を提供することです。(関数ポインタと同様に)

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;

public class Main {
	static class ClassA {
		public void println(String s) {
			System.out.println(s);
		}
	}

	public static void main(String[] args) throws Throwable {
		Object obj = System.currentTimeMillis() % 2 == 0 ? System.out : new ClassA();
		// 无论obj最终是哪个实现类,下面这句都能正确调用到println方法。
		getPrintlnMH(obj).invokeExact("icyfenix");
	}

	private static MethodHandle getPrintlnMH(Object reveiver) throws Throwable {
		// MethodType:代表“方法类型”,包含了方法的返回值(methodType()的第一个参数)和具体参数(methodType()第二个及以后的参数)。
		MethodType mt = MethodType.methodType(void.class, String.class);
		// lookup()方法来自于MethodHandles.lookup
		// 作用是在指定类中查找符合给定的方法名称、方法类型,并且符合调用权限的方法句柄。
		// 因为这里调用的是一个虚方法,按照Java语言的规则,方法第一个参数是隐式的,代表该方法的接收者
		// 也即this指向的对象,这个参数以前是放在参数列表中进行传递,现在提供了bindTo()方法来完成这件事情。
		return MethodHandles.lookup().findVirtual(reveiver.getClass(), "println", mt).bindTo(reveiver);
	}
}

この方法getPrintlnMHは()実際にINVOKEVIRTUAL命令のシミュレート実行ですが、ディスパッチ論理クラスファイルのバイトコード上で硬化されていませんが、Javaは、ユーザーが設計した方法によって達成されます。この方法自体(MethodHandleオブジェクト)の戻り値は、方法の最後の呼び出しのように見ることができる「参照」。

異なっ反射:

  • 自然とメカニズムの反射はMethodHandleシミュレーションメソッド呼び出しであります
    • 反射シミュレーション方法は、Javaコードのレベルを呼び出し、
    • MethodHandleシミュレーション方法は、レベルバイトコードと呼ばれる
      三つの方法MethodHandles.Lookup findStatic(中)、findVirtual()、 findSpecial() 動作invokestatic、INVOKEVIRTUAL、及びこれらinvokespecialバイトコード命令のいくつかをチェックする権限を実行するために対応します、これらの低レベルの詳細リフレクションAPIを使用して気にする必要はありません。
  • 情報java.lang.invoke.MethodHandleは、java.lang.reflect.Methodオブジェクトがはるかに含まれてはるかにMethodHandleメカニズムにリフレクションオブジェクトオブジェクト。
    • 反射署名法を含む画像のJava側で包括的な方法である、Javaメソッドとプロパティは、様々な特性の記述子テーブルの表現を終了、また、実行権限などのランタイム情報を含みます。
    • MethodHandleは、メソッドを実行するための情報のみが含まれています。
      リフレクションはヘビー級、軽量かつMethodHandleです。
  • MethodHandleは、命令バイトコードと呼ばれる方法のシミュレーション、この点でMethodHandle上のさまざまな最適化(インライン化法)は、(類似したのアイデアをサポートするために使用されるだけでなく、達成されるべきであることを理論的に仮想マシンですので、中)で改善し続け、およびメソッドを呼び出すための反射によって呼び出しサイトを最適化するために様々な施策の実施に直接移動することはほとんど不可能です。
  • リフレクションAPIのみをJava言語のサービスのために設計されており、MethodHandleだけJava言語を含め、すべてのJava仮想マシン語の上に機能するように設計されています。
invokedynamicの

役割invokedynamicの命令MethodHandleメカニズムは同じですが、完全に仮想マシン内で硬化させた「指示方法ディスパッチルールを解決することで、ターゲットの方法を見つける方法を決定する権利は、特定のユーザーコードの中に仮想マシンからLETを通過しましたユーザーの自由度が高い。
さらに、彼らはまた、両方のアナログのアイデアです、それは同じ目的を達成することであるが、コードやAPI上位層を実装するための、および他のプロパティを持つ別のクラスのバイトコードと、定数が完了します。

命令を含むinvokedynamicのすべての場所は、「動的呼び出しポイント(動的に計算されたコールサイト)」と呼ばれています。この命令の最初のパラメータはCONSTANT_Methodref_info一定のシンボリック参照の方法をもはや代表ではありませんが、CONSTANT_InvokeDynamic_info定数となり、あなたは、この新しい3つの定数から情報を得ることができます。

  • ブート方法(ブートストラップ法):指定された固定パラメータと戻り値はjava.lang.invoke.CallSite対象である、このオブジェクトは、実際の呼び出しを表すターゲットメソッドが実行されます。
  • メソッドタイプ(MethodType)
  • 名前

CONSTANT_InvokeDynamic_info定数を提供された情報によれば、仮想マシンはターゲットメソッドへの最後の呼び出しを実行する、呼び出し場所のオブジェクトを取得するためにブート方法を見つけて実行することができます。

スタックベースの実行エンジン通訳バイトコード

解釈

コンパイルプロセス:

バイトコード命令ストリームリニア構文木トラバーサルを生成し、その後完了字句解析した後に、Java言語、javacコンパイラ・プログラム・コード、抽象構文木に構文解析では、と。
コンパイラJavaプログラムので、仮想マシン内のインタプリタは、半独立実装である一方、アクションの一部は、Java仮想マシンの外で行われるためです。

スタックベースの命令セット

コンパイラによってjavacのバイトコード命令ストリーム出力は、本質的にバイトコード命令ストリームは、ほとんどゼロアドレス命令のオペランドに依存しているでスタックベースの命令セットアーキテクチャ(命令セットアーキテクチャ、ISA)、ありますスタックの仕事。

命令スタックに基づいてセットの主な利点:

  • ポータブル
  • コードは、比較的コンパクトであります
  • コンパイラは簡単に達成するために

命令スタックに基づいて、現在の主な欠点:

  • 遅いペース
    • 命令のより多くの数
    • マルチアクセス・メモリ





Cenkaoziliao:周志明と:「JVMは機能を高度なベストプラクティス(第3版)Java仮想マシンの深い理解」

おすすめ

転載: www.cnblogs.com/JL916/p/12446021.html