虛拟機位元組碼執行引擎
- 一、運作時棧幀結構
- 1.局部變量表
- ①局部變量表必須賦初始值
- ②如何存儲和占用記憶體大小
- ③slot可以複用
- 2.操作數棧
- ①定義和工作過程
- ②操作數棧共享區域
- 3.動态連結
- 4.方法傳回位址
- 兩種退出方式
- 5.附加資訊
- 二、方法調用
- 方法調用
- 1.解析調用
- 2.分派調用
- ①靜态分派調用
- ②動态分派調用
- 三、動态語言支援

前面研究了位元組碼結構,類加載的過程,現在了解,位元組碼執行。
一、運作時棧幀結構
1.局部變量表
①局部變量表必須賦初始值
②如何存儲和占用記憶體大小
使用slot存儲,根據資料類型有使用1個slot的類型,也有2個slot的類型的。
引起線程安全的三個條件:
① 多線程
②共享資源
③共享資源進行非原子性操作
③slot可以複用
代碼示範:
public class GCDemo {
public static void main(String[] args) {
{
byte[] buff = new byte[60 * 1024 * 1024];
}
//int a = 10;
System.gc();
}
}
打開注釋代碼: int a = 10;
原因:
2.操作數棧
①定義和工作過程
Java虛拟機的解釋執行引擎被稱為"基于棧的執行引擎",其中所指的棧就是指-操作數棧。
操作數棧也常被稱為操作棧。
和局部變量區一樣,操作數棧也是被組織成一個以字長為機關的數組。但是和前者不同的是,它不是通過索引來通路,而是通過标準的棧操作—壓棧和出棧—來通路的。比如,如果某個指令把一個值壓入到操作數棧中,稍後另一個指令就可以彈出這個值來使用。
虛拟機在操作數棧中存儲資料的方式和在局部變量區中是一樣的:如int、long、float、double、reference和returnType的存儲。對于byte、short以及char類型的值在壓入到操作數棧之前,也會被轉換為int。
虛拟機把操作數棧作為它的工作區——大多數指令都要從這裡彈出資料,執行運算,然後把結果壓回操作數棧。比如,iadd指令就要從操作數棧中彈出兩個整數,執行加法運算,其結果又壓回到操作數棧中,看看下面的示例,它示範了虛拟機是如何把兩個int類型的局部變量相加,再把結果儲存到第三個局部變量的:
- begin
- iload_0 // push the int in local variable 0 onto the stack
- iload_1 // push the int in local variable 1 onto the stack
- iadd // pop two ints, add them, push result
- istore_2 // pop int, store into local variable 2
- end
在這個位元組碼序列裡,前兩個指令iload_0和iload_1将存儲在局部變量中索引為0和1的整數壓入操作數棧中,其後iadd指令從操作數棧中彈出那兩個整數相加,再将結果壓入操作數棧。第四條指令istore_2則從操作數棧中彈出結果,并把它存儲到局部變量區索引為2的位置。下圖詳細表述了這個過程中局部變量和操作數棧的狀态變化,圖中沒有使用的局部變量區和操作數棧區域以空白表示。
②操作數棧共享區域
3.動态連結
在Class檔案中的常量持中存有大量的符号引用。位元組碼中的方法調用指令就以常量池中指向方法的符号引用作為參數。這些符号引用一部分在類的加載階段(解析)或第一次使用的時候就轉化為了直接引用(指向資料所存位址的指針或句柄等),這種轉化稱為靜态連結。而相反的,另一部分在運作期間轉化為直接引用,就稱為動态連結。
與那些在編譯時進行連結的語言不同,Java類型的加載和連結過程都是在運作的時候進行的,這樣雖然在類加載的時候稍微增加一些性能開銷,但是卻能為Java應用程式提供高度的靈活性,Java中天生可以動态擴充的語言特性就是依賴動态加載和動态連結這個特點實作的。
動态擴充就是在運作期可以動态修改位元組碼,也就是反射機制與cglib。
4.方法傳回位址
兩種退出方式
5.附加資訊
二、方法調用
方法調用
1.解析調用
以下四種情況不能重載或者從寫:
也就對應下面的指令:
執行個體:寫個例子看下解析調用已經确定好了。示範如下:
public class Hello {
public static void sayHello(){
System.out.println("hello world");
}
public static void main(String[] args) {
Hello.sayHello();
}
}
編譯後,執行
javap -verbose Hello.class
.
可以看到main方法中的invokestatic指令指定了sayHello,方法已經确定,不可修改,這就是方法調用解析:
2.分派調用
①靜态分派調用
靜态分派也是在編譯階段就可确定。
例子:
public class Demo {
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();
Demo demo = new Demo();
demo.sayHello(man);
demo.sayHello(woman);
}
}
運作結果:
why?
執行javap -verbose Dmeo.class再來驗證下:看下
②動态分派調用
例子:
public class Demo {
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();
}
}
運作結果:
why?