JAVA 仮想マシン (JVM) の詳細な紹介 (6) - バイトコード実行エンジン

王林
リリース: 2019-08-24 15:57:14
転載
2690 人が閲覧しました

JVM の実行エンジンが Java コードを実行する場合、通常、インタープリタ実行 (インタープリタを介した実行) とコンパイル済み実行 (ジャストインタイム コンパイラを介して生成されたローカル コード実行) の 2 つのオプションがあります。

スタック フレーム

定義:

スタック フレームは、仮想マシンによるメソッド呼び出しとメソッド実行をサポートするために使用されるデータです。仮想マシン スタック内にある構造。

関数:

呼び出しの開始から実行の完了までのすべてのメソッドは、仮想マシン スタック内のスタックからスタックまでのスタック フレームに対応します。のプロセス。

JAVA 仮想マシン (JVM) の詳細な紹介 (6) - バイトコード実行エンジン

特徴:

(1) スタックフレームにはローカル変数テーブル、オペランドスタックなどが含まれます。必要ですか? ローカル変数テーブルとオペランド スタックの深さはコンパイル時に決定されます。スタック フレームに割り当てる必要があるメモリの量は、プログラム実行時の変数データの影響を受けないためです。

(2) 2 つのスタック フレーム間のデータ共有。概念モデルでは 2 つのスタック フレームは完全に独立していますが、仮想マシンの実装では、2 つのスタック フレームが部分的に重なるように最適化処理が行われます。このようにして、追加のパラメーターのコピーや受け渡しを必要とせずに、メソッド呼び出しを行うときにデータの一部を共有できます。

JAVA 仮想マシン (JVM) の詳細な紹介 (6) - バイトコード実行エンジン

(1) ローカル変数テーブル

ローカル変数テーブルは、変数値の格納セットです。スペース: メソッド パラメータとメソッド内で定義されたローカル変数を格納するために使用されます。

//方法参数   
max(int a,int b)
ログイン後にコピー
int a;//全局变量
void say(){
   int b=0;//局部变量
 }
ログイン後にコピー

ローカル変数はクラス変数(静的に変更された変数)とは異なります

クラス変数には、準備フェーズ(システム初期値の割り当て)と初期化フェーズの 2 つの初期値割り当てプロセスがあります。 (プログラマが定義した初期値を割り当てます)。したがって、初期化フェーズ中にクラス変数に値が割り当てられていなくても、特定の初期値が保持されているため、問題はありません。
ただし、ローカル変数は異なり、定義しても初期値が代入されていない場合は使用できません。

(2) オペレーション スタック

メソッドの実行が開始されたばかりのとき、このメソッドのオペランド スタックは空です。オペランド スタックに内容を書き込み、抽出するためのさまざまなバイトコード命令、つまりポップおよびプッシュ操作があります。

たとえば、次のように計算します。

int a=2+3
ログイン後にコピー

オペランド スタックの先頭に最も近い 2 つの要素は 2 と 3 です。iadd 命令が実行されると、2 と 3 がスタックからポップされます。そして追加されました。 、そして追加された結果 5 をスタックにプッシュします。

(3) ダイナミックリンク

#クラスファイルの定数プールには多数のシンボル参照があり、メソッド呼び出しが行われます。バイトコード内の命令 定数プール内のメソッドへのシンボリック参照をパラメータとして取得するだけです。これらのシンボル参照は 2 つの部分に分かれています。

静的解析: クラスの読み込みフェーズ中または初めて使用されるときに、直接参照に変換されます。ダイナミックリンク: 各実行中に直接参照に変換されます。

(4) 返送先住所


当一个方法开始执行后,只有两种方式可以退出这个方法:正常退出、异常退出。无论采用何种退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行。

当方法正常退出时

调用者的PC计数器作为返回地址。栈帧中一般会保存这个计数器值。

当方法异常退出时

返回地址是要通过异常处理器表来确定的。栈帧中一般不会保存这部分信息。

方法调用

方法调用是确定调用哪一个方法。

(1)解析

对“编译器可知,运行期不可变”的方法进行调用称为解析。符合这种要求的方法主要包括

静态方法,用static修饰的方法私有方法,用private修饰的方法

(2)分派

分派讲解了虚拟机如何确定正确的目标方法。分派分为静态分派和动态分派。讲解静动态分派之前,我们先看个多态的例子。

Human man=new Man();
ログイン後にコピー

在这段代码中,Human为静态类型,其在编译期是可知的。Man是实际类型,结果在运行期才可确定,编译期在编译程序的时候并不知道一个对象的实际类型是什么。

静态分派:

所有依赖静态类型来定位方法执行版本的分派动作称为静态分派。它的典型应用是重载。

public class StaticDispatch{  
   static abstract class Human{  
    }  
   static class Man extends Human{
    }
    static class Woman extends Human{
    }
    public void say(Human hum){  
        System.out.println("I am human");  
    }  
    public void say(Man hum){  
        System.out.println("I am man");  
    }  
    public void say(Woman hum){  
        System.out.println("I am woman");  
    }  
    
    public static void main(String[] args){  
        Human man = new Man();  
        Human woman = new Woman();  
        StaticDispatch sr = new StaticDispatch();  
        sr.say(man);  
        sr.say(woman);  
    }  
}
ログイン後にコピー

运行结果是:

I am human
I am human
ログイン後にコピー

为什么会产生这个结果呢?
因为编译器在重载时,是通过参数的静态类型而不是实际类型作为判断依据的。在编译阶段,javac编译器会根据参数的静态类型决定使用哪个重载版本,所以两个对say()方法的调用实际为sr.say(Human)。

动态分派:

在运行期根据实际类型确定方法执行版本的分派过程。它的典型应用是重写。

public class DynamicDispatch{  
   static abstract class Human{  
            protected abstract void say();
    }  
   static class Man extends Human{
            @Override
             protected abstract void say(){
             System.out.println("I am man");  
            }
    }
    static class Woman extends Human{
         @Override
             protected abstract void say(){
             System.out.println("I am woman ");  
            }
    }
    
    public static void main(String[] args){  
        Human man = new Man();  
        Human woman = new Woman();  
        man.say();
        woman.say();
        man=new Woman();
        man.say();
    }  
}
ログイン後にコピー

运行结果:

I am man
I am woman 
I am woman
ログイン後にコピー

这似乎才是我们平时敲的java代码。对于方法重写,在运行时才确定调用哪个方法。由于Human的实际类型是man,因此调用的是man的name方法。其余的同理。

动态分派的实现依赖于方法区中的虚方法表,它里面存放着各个方法的实际入口地址。如果某个方法在子类中被重写了,那子类方法表中的地址将会替换为指向子类实现版本的入口地址,否则,指向父类的实现入口。

单分派和多分派:

方法的接收者与方法的参数统称为方法的宗量,根据分派基于多少种宗量,分为单分派和多分派。

在静态分派中,需要调用者的实际类型和方法参数的类型才能确定方法版本,所以其是多分派类型。在动态分派中,已经知道了参数的实际类型,所以此时只需知道方法调用者的实际类型就可以确定出方法版本,所以其是单分派类型。综上,java是一门静态多分派,动态单分派的语言。

字节码解释执行引擎

虚拟机中的字节码解释执行引擎是基于栈的。下面通过一段代码来仔细看一下其解释的执行过程。

public int calc(){  
    int a = 100;  
    int b = 200;  
    int c = 300;  
    return (a + b) * c;  
}
ログイン後にコピー

第一步:将100入栈。

JAVA 仮想マシン (JVM) の詳細な紹介 (6) - バイトコード実行エンジン

ステップ 2: 操作スタックから 100 をポップし、ローカル変数に保存します。後続の 200,300 についても同様です。

JAVA 仮想マシン (JVM) の詳細な紹介 (6) - バイトコード実行エンジン

ステップ 3: ローカル変数テーブルの 100 をオペランド スタックの先頭にコピーします。

JAVA 仮想マシン (JVM) の詳細な紹介 (6) - バイトコード実行エンジン

ステップ 4: ローカル変数テーブルの 200 をオペランド スタックの先頭にコピーします。

JAVA 仮想マシン (JVM) の詳細な紹介 (6) - バイトコード実行エンジン

ステップ 5: 100 と 200 をスタックからポップし、整数の加算を実行し、最後に結果の 300 をスタックにプッシュします。

JAVA 仮想マシン (JVM) の詳細な紹介 (6) - バイトコード実行エンジン

ステップ 6: ローカル変数テーブルから 3 番目の数値 300 をスタックの先頭にコピーします。次のステップでは、2 つの 300 をスタックからポップし、整数乗算を実行して、最終結果 90000 をスタックにプッシュします。

JAVA 仮想マシン (JVM) の詳細な紹介 (6) - バイトコード実行エンジン

ステップ 7: メソッドは終了し、オペランド スタックの最上位の整数値がこのメソッドの呼び出し元に返されます。

JAVA 仮想マシン (JVM) の詳細な紹介 (6) - バイトコード実行エンジン

上記は、JAVA 仮想マシン バイトコード実行エンジンの完全な紹介です。その他の関連する質問については、PHP 中国語 Web サイトを参照してください: JAVA ビデオ チュートリアル

以上がJAVA 仮想マシン (JVM) の詳細な紹介 (6) - バイトコード実行エンジンの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
jvm
ソース:csdn.net
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!