運行時棧幀結(jié)構(gòu)
- 棧幀是虛擬機棧中的元素,每一個方法的調(diào)用對應(yīng)著一個棧幀的入棧出棧豁生。棧幀包括局部變量表、操作數(shù)棧漫贞、動態(tài)鏈接甸箱、方法返回地址等信息。
- 在編譯階段绕辖,棧幀中需要多大的局部變量表和多深的操作數(shù)棧都是已經(jīng)確定的摇肌,并且寫入到方法表的Code屬性中。
局部變量表
- 編譯期間仪际,就在方法的Code屬性的max_locals數(shù)據(jù)項中確定了該方法所需要分配的局部變量表的最大容量
- 局部變量表已slot為最小單位,一個slot32位昵骤。long和double占兩個(不存在線程問題)树碱。第0個slot存放this,從1開始按照變量順序和作用域分配slot变秦。
- 類變量在準備和初始化階段會復(fù)制好成榜,局部變量沒有這個東西,必須手工復(fù)制才能使用蹦玫。
操作數(shù)棧
- 和局部變量表一樣赎婚,操作數(shù)棧的最大深度在編譯時寫入到了Code屬性的max_stacks中刘绣。32位1個容量,64位2個容量挣输。
- Java虛擬機的解釋執(zhí)行引擎稱為“基于棧的執(zhí)行引擎”纬凤,就是指這個。
動態(tài)鏈接
- 每個棧幀都包含一個指向運行時常量池中該棧幀所屬方法的引用撩嚼,持有該引用是為了支持方法調(diào)用過程中的動態(tài)連接停士。
方法返回地址
- 執(zhí)行引擎遇到任意一個方法返回的字節(jié)碼指令,也就是所謂的正常完成出口完丽。
- 在方法執(zhí)行的過程中遇到了異常恋技,并且這個異常沒有在方法內(nèi)進行處理,也就是只要在本方法的異常表中沒有搜索到匹配的異常處理器逻族,就會導(dǎo)致方法退出蜻底,這種方式成為異常完成出口。
- 無論通過哪種方式退出聘鳞,在方法退出后都返回到該方法被調(diào)用的位置薄辅,方法正常退出時,調(diào)用者的pc計數(shù)器的值作為返回地址搁痛,而通過異常退出的长搀,返回地址是要通過異常處理器表來確定,棧幀中一般不會保存這部分信息鸡典。本質(zhì)上源请,方法的退出就是當(dāng)前棧幀出棧的過程。
方法調(diào)用
- 不是方法執(zhí)行彻况,而是確定被調(diào)用方法的版本(哪個方法)谁尸,不涉及運行。因為Class文件中存放的是符號引用纽甘,不是直接引用良蛮。
有兩種調(diào)用方式
- 解析調(diào)用:在編譯器就確定目標方法。
- 分派調(diào)用:重載悍赢、重寫决瞳。
解析
類加載的解析階段,會將一部分符號引用轉(zhuǎn)化為直接引用左权,前提是:方法在程序運行之前就有一個可確定的調(diào)用版本皮胡,并且運行期不可變。
- 主要包括靜態(tài)方法和私有方法赏迟。
- 解析調(diào)用是靜態(tài)的過程屡贺,在編譯期間就完全確定,在類加載的解析階段就會把符號引用轉(zhuǎn)化為直接引用。
分派
靜態(tài)分派:依賴靜態(tài)類型類確定方法執(zhí)行版本甩栈。典型應(yīng)用:重載泻仙。
解析和分派不是排他的,靜態(tài)分派會在類加載器就進行解析量没,而靜態(tài)方法的重載玉转,也是通過靜態(tài)分派確定重載版本的。
public class StaticDispatch {
static abstract class Human{
}
static class Man extends Human{
}
static class Woman extends Human{
}
public void sayHello(Human human) {
System.out.println("hello human");
}
public void sayHello(Man man) {
System.out.println("hello man");
}
public void sayHello(Woman woman) {
System.out.println("hello woman");
}
public static void main(String[] args) {
Human man = new Man();
Human woman = new Woman();
StaticDispatch sd = new StaticDispatch();
sd.sayHello(man);
sd.sayHello(woman);
}
}
output:
都輸出hello human
Human man = new Man();
Human 是變量的靜態(tài)類型允蜈,編譯期可知的冤吨。
Man是變量的實際類型,運行期才確定饶套。
動態(tài)分派:運行期根據(jù)實際類型確定方法的執(zhí)行版本漩蟆。典型應(yīng)用:重寫。
public class DynamicDispatch {
static abstract class Human{
protected abstract void sayHello();
}
static class Man extends Human{
public void sayHello() {
System.out.println("hello man");
}
}
static class Woman extends Human{
public void sayHello() {
System.out.println("hello woman");
}
}
public static void main(String[] args) {
Human man = new Man();
Human woman = new Woman();
man.sayHello();
woman.sayHello();
}
}
output:
hello man
hello woman
*虛擬機如何實現(xiàn)動態(tài)分派妓蛮?
因為動態(tài)分派是非常頻繁的動作怠李,而且選擇方法版本時需要運行時在類的方法元數(shù)據(jù)中搜索合適的目標方法,所以基于性能蛤克,虛擬機為類在方法區(qū)中建立了一個虛方法表捺癞。使用虛方法表索引來代替元數(shù)據(jù)以提高性能。
虛方法中存放著各個方法的實際入口地址构挤,如果子類沒有重寫父類的方法髓介,那么就存放著父類方法的入口地址;如果重寫了筋现,就存放子類實現(xiàn)方法的入口地址唐础。
基于棧的指令集和基于寄存器的指令集
基于棧:可移植,速度慢矾飞。
基于寄存器:速度快一膨。