Wenn die Ausführungs-Engine in der JVM Java-Code ausführt, stehen ihr im Allgemeinen zwei Optionen zur Verfügung: interpretierte Ausführung (Ausführung durch einen Interpreter) und kompilierte Ausführung (lokale Code-Ausführung, die durch einen Just-in-Time-Compiler generiert wird).
Stack-Frame
Definition:
Stack-Frame sind Daten, die zur Unterstützung des Methodenaufrufs und der Methodenausführung durch die virtuelle Maschine verwendet werden Struktur, die sich innerhalb des Stapels der virtuellen Maschine befindet.
Funktion:
Vom Beginn des Aufrufs bis zum Abschluss der Ausführung entspricht jede Methode einem Stapelrahmen, der in den Stapel der virtuellen Maschine geschoben und herausgesprungen wird des Stapelprozesses.
Funktionen:
(1) Der Stapelrahmen enthält die lokale Variablentabelle, den Operandenstapel usw. Wie groß ist er? benötigt? Die lokale Variablentabelle und die Tiefe des Operandenstapels werden zur Kompilierzeit bestimmt. Denn wie viel Speicher einem Stapelrahmen zugewiesen werden muss, wird während der Programmlaufzeit nicht durch variable Daten beeinflusst.
(2) Datenaustausch zwischen zwei Stapelrahmen. Im konzeptionellen Modell sind die beiden Stapelrahmen völlig unabhängig, aber bei der Implementierung der virtuellen Maschine wird eine gewisse Optimierungsverarbeitung durchgeführt, um die beiden Stapelrahmen teilweise zu überlappen. Auf diese Weise kann ein Teil der Daten bei Methodenaufrufen gemeinsam genutzt werden, ohne dass zusätzliches Kopieren und Übergeben von Parametern erforderlich ist.
(1) Lokale Variablentabelle
Die lokale Variablentabelle ist eine Reihe von Variablenwertspeichern Leerzeichen werden zum Speichern von Methodenparametern und lokalen Variablen verwendet, die innerhalb der Methode definiert sind.
//方法参数 max(int a,int b)
Lokale Variablen unterscheiden sich von Klassenvariablen (mit statischen Variablen modifizierte Variablen)
Klassenvariablen haben zwei Prozesse zum Zuweisen von Anfangswerten: die Vorbereitungsphase (Zuweisung des Systemanfangswerts) und die Initialisierungsphase (Zuweisung eines vom Programmierer definierten Anfangswerts). Es spielt also keine Rolle, wenn der Klassenvariablen während der Initialisierungsphase kein Wert zugewiesen wird, sie hat immer noch einen bestimmten Anfangswert.
Lokale Variablen sind jedoch anders. Wenn sie definiert sind, aber kein Anfangswert zugewiesen ist, können sie nicht verwendet werden.
(2) Operationsstapel
Wenn eine Methode gerade mit der Ausführung beginnt, ist der Operandenstapel dieser Methode während des Ausführungsprozesses leer. Es gibt verschiedene Bytecode-Anweisungen zum Schreiben und Extrahieren von Inhalten aus dem Operandenstapel, dh Pop- und Push-Operationen.
Berechnen Sie beispielsweise:
int a;//全局变量 void say(){ int b=0;//局部变量 }
Die beiden Elemente, die sich am nächsten an der Spitze des Operandenstapels befinden, sind 2 und 3. Wenn die iadd-Anweisung ausgeführt wird, werden 2 und 3 vom Stapel entfernt und addiert, und dann das addierte Ergebnis 5 auf den Stapel schieben.
(3) Dynamischer Link
Der Konstantenpool der Klassendatei enthält eine große Anzahl von Symbolreferenzen und Anweisungen zum Methodenaufruf Nehmen Sie im Bytecode einfach einen symbolischen Verweis auf die Methode im Konstantenpool als Parameter. Diese Symbolreferenzen sind in zwei Teile unterteilt:
Statische Auflösung: Während der Klassenladephase oder bei der ersten Verwendung in direkte Referenzen umgewandelt. Dynamischer Link: bei jedem Lauf in eine direkte Referenz umgewandelt.
(4) Rücksendeadresse
当一个方法开始执行后,只有两种方式可以退出这个方法:正常退出、异常退出。无论采用何种退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行。
当方法正常退出时
调用者的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入栈。
Schritt 2: Nehmen Sie 100 aus dem Operationsstapel und speichern Sie es in lokalen Variablen. Das Gleiche gilt für die folgenden 200.300.
Schritt 3: Kopieren Sie 100 in der lokalen Variablentabelle an die Spitze des Operandenstapels.
Schritt 4: Kopieren Sie 200 in der lokalen Variablentabelle an die Spitze des Operandenstapels.
Schritt 5: Nehmen Sie 100 und 200 vom Stapel, führen Sie eine Ganzzahladdition durch und schieben Sie schließlich das Ergebnis 300 zurück auf den Stapel.
Schritt 6: Kopieren Sie die dritte Zahl 300 aus der lokalen Variablentabelle an die Spitze des Stapels. Der nächste Schritt besteht darin, die beiden 300er vom Stapel zu nehmen, eine Ganzzahlmultiplikation durchzuführen und das Endergebnis 90000 auf den Stapel zu legen.
Schritt 7: Die Methode endet und der ganzzahlige Wert oben im Operandenstapel wird an den Aufrufer dieser Methode zurückgegeben.
Das Obige ist eine vollständige Einführung in die JAVA Virtual Machine – Bytecode-Ausführungs-Engine. Weitere verwandte Fragen finden Sie auf der chinesischen PHP-Website: JAVA Video Tutorial
Das obige ist der detaillierte Inhalt vonDetaillierte Einführung in JAVA Virtual Machine (JVM) (6) – Bytecode-Ausführungs-Engine. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!