一病毡、運(yùn)行時(shí)棧幀結(jié)構(gòu)
棧幀(Stack Frame)是用于JVM執(zhí)行方法調(diào)用和方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu)祭务,是虛擬機(jī)棧的元素。棧幀存儲(chǔ)了方法的局部變量表丁溅、操作數(shù)棧唤蔗、動(dòng)態(tài)鏈接和和方法返回地址等信息。每一個(gè)方法從調(diào)用開(kāi)始到執(zhí)行結(jié)束,都對(duì)應(yīng)著一個(gè)棧幀的入棧到出棧妓柜。
一個(gè)線程中方法調(diào)用鏈可能會(huì)很長(zhǎng)箱季,在活動(dòng)線程中,只有位于棧頂?shù)臈?/strong>才是有效的棍掐,成為當(dāng)前棧幀藏雏,執(zhí)行引擎運(yùn)行的所有字節(jié)碼指令,都只針對(duì)當(dāng)前棧幀進(jìn)行操作作煌。
在調(diào)用實(shí)例方法(非static方法)時(shí)掘殴,默認(rèn)第0位的slot傳遞的是方法所屬對(duì)象實(shí)例的引用(也就是方法的接收者,JVM在調(diào)用方法時(shí)粟誓,會(huì)去方法接收者那里)奏寨,方法中可以通過(guò)this訪問(wèn)到這個(gè)隱含的參數(shù)。
1.1 局部變量表
一組變量值存儲(chǔ)空間鹰服,用于存放方法參數(shù)和局部變量病瞳。
表中的slot是可以復(fù)用(不是已經(jīng)復(fù)用)的,如果當(dāng)前程序計(jì)數(shù)器的值已經(jīng)超過(guò)某個(gè)局部變量的作用于悲酷,那么變量對(duì)應(yīng)的Slot就可以給其他變量使用套菜。
請(qǐng)思考以下代碼是否可以收回局部變量空間?
public static void main(String[] args)
{
{
byte[] placeHolder = new byte[64 * 1024 * 1024];
}
System.gc();
}
分析:placeHolder是否被回收的關(guān)鍵舔涎,在于局部變量表中的Slot是否還存有對(duì)象placeHolder的引用笼踩。gc的時(shí)候,雖然已經(jīng)離開(kāi)了placeHolder的作用域亡嫌,但是在此之后嚎于,沒(méi)有對(duì)于局部變量表的任何讀寫操作,placeHolder所占用的slot還沒(méi)有被復(fù)用挟冠,所以作為GC Roots的局部變量表仍然保持著對(duì)它的關(guān)聯(lián)于购。
注意:
類變量或?qū)嵗兞浚瑳](méi)有賦值也可以使用知染,因?yàn)橛辛阒道呱5蔷植孔兞繘](méi)有賦值則不能使用。(估計(jì)原因是局部變量很多控淡,作用域有很短嫌吠,消亡很快,如果都賦零值掺炭,性能影響比較大辫诅,沒(méi)有必要)
1.2 操作數(shù)棧(Operand Stack)
方法剛開(kāi)始執(zhí)行時(shí),操作數(shù)棧是空的涧狮。在執(zhí)行過(guò)程中炕矮,會(huì)有各種指令往操作數(shù)棧中讀寫內(nèi)容么夫。
1.3 動(dòng)態(tài)鏈接
每個(gè)棧幀都包含一個(gè)指向運(yùn)行時(shí)常量池中,該棧幀所屬方法的引用肤视。字節(jié)碼中的方法調(diào)用指令就以常量池中指向方法的符號(hào)引用作為參數(shù)
1.4 方法返回地址
正常退出時(shí)档痪,調(diào)用者的PC計(jì)數(shù)器的值可以作為返回地址邢滑。
二腐螟、方法調(diào)用
方法調(diào)用并不等于方法執(zhí)行,方法調(diào)用階段唯一任務(wù)就是確定被調(diào)用方法的版本(即調(diào)用哪個(gè)方法)殊鞭。
2.1 解析調(diào)用
所有方法在class文件中都是常量池中的符號(hào)引用遭垛,在類解析階段
- 一部分符號(hào)引用會(huì)轉(zhuǎn)化為直接引用
前提:編譯期可知,運(yùn)行期不變操灿。符合這個(gè)要求的主要包括靜態(tài)方法和私有方法锯仪。
解析調(diào)用一定是靜態(tài)的過(guò)程,在編譯期就完全確定趾盐,在類加載的解析階段就會(huì)把涉及的符號(hào)引用替換為可確定的直接引用庶喜。
2.2 分派調(diào)用
Human human = new Man();
Human 稱為變量的靜態(tài)類型,Man稱為實(shí)際類型救鲤。
靜態(tài)分派
依賴靜態(tài)類型來(lái)定位方法的分派動(dòng)作久窟,稱為靜態(tài)分派。
重載(Overload)屬于靜態(tài)分派本缠。
注:
如果參數(shù)是類似byte斥扛、char、int丹锹、Object的重載稀颁,定位方法時(shí)會(huì)在可以轉(zhuǎn)換的前提下,從小到大楣黍,依次匹配匾灶。動(dòng)態(tài)分派
覆蓋(Override)屬于動(dòng)態(tài)分派。
Human man = new Man();
Human woman = new Woman();
man.sayHello();
woman.sayHello();
man 和 woman 是將要執(zhí)行的sayHello()方法的所有者租漂,稱為接收者阶女。方法的接收者和參數(shù),統(tǒng)稱為方法的宗量哩治。根據(jù)分派基于多少種宗量秃踩,可將分派劃分為單分派和多分派。
public class Dispatch {
class QQ {}
class 360 {}
public class Father
{
public hardChoice (QQ qq)
{
System.out.println("Father choice QQ.");
}
public hardChoice (360 args)
{
System.out.println("Father choice 360.");
}
}
public class Son
{
public hardChoice (QQ qq)
{
System.out.println("Son choice QQ.");
}
public hardChoice (360 args)
{
System.out.println("Son choice 360.");
}
}
public static void main(String[] args)
{
Father father = new Father();
Father son = new Son();
father.hardChoice(new _360());
son.hardChoice(new QQ());
}
最終決定調(diào)用方法版本的因素憔杨,就是方法的宗量(方法接收者(實(shí)際類型) + 參數(shù)類型(靜態(tài)類型))
public void doSomeThing(Map map)
{
// doSomeThing
}
// 調(diào)用方法
doSomeThing(new HashMap());
所以其實(shí)這個(gè) new HashMap(), 會(huì)被隱含的轉(zhuǎn)為 (Map)new HashMap(),
也即用的是參數(shù)的靜態(tài)類型去調(diào)用方法驾孔。
- JVM 動(dòng)態(tài)分派的實(shí)現(xiàn)
為類在方法區(qū)建立一個(gè)虛方法表。
虛方法表存放著各個(gè)方法的實(shí)際入口地址翠勉,如果某個(gè)方法在子類沒(méi)有override妖啥,那子類虛方法表中入口地址和父類的一樣。如果覆蓋了对碌,子類的方法表中的入口地址會(huì)替換為子類版本的入口地址。