本文主要內(nèi)容
- 前言
- 運(yùn)行時(shí)棧楨結(jié)構(gòu)
已經(jīng)學(xué)習(xí)了虛擬機(jī)內(nèi)存區(qū)域、Class文件結(jié)構(gòu)、類加載機(jī)制等知識(shí),是時(shí)候?qū)W習(xí)虛擬機(jī)字節(jié)碼執(zhí)行過程了。
前言
虛擬機(jī)是一個(gè)相對(duì)物理機(jī)而言的概念,它們都有代碼執(zhí)行能力,其區(qū)別是物理機(jī)的執(zhí)行引擎是直接建立在處理器、硬件、指令集和操作系統(tǒng)層面的。而虛擬機(jī)的執(zhí)行引擎則是自己實(shí)現(xiàn)的。
一個(gè)Java應(yīng)用程序就對(duì)應(yīng)著一個(gè)虛擬機(jī)進(jìn)程,虛擬機(jī)根據(jù)規(guī)范,自己操作內(nèi)存、解析字節(jié)碼并執(zhí)行。
運(yùn)行時(shí)棧楨結(jié)構(gòu)
棧楨是用于支持虛擬機(jī)進(jìn)行方法調(diào)用和方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu),它是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)中的虛擬機(jī)棧的棧元素。
棧楨中存儲(chǔ)了方法的局部變量表、操作數(shù)棧、動(dòng)態(tài)連接和方法返回地址等信息。
每一個(gè)方法從調(diào)用開始到執(zhí)行完成的過程,就對(duì)應(yīng)著一個(gè)棧楨在虛擬機(jī)棧里入棧到出棧的過程。

在編譯代碼的時(shí)候,棧楨中需要多大的局部變量表、多深的操作數(shù)棧都已經(jīng)完全確定了,并且寫入到方法的Code屬性了。

如上圖,stack = 1,表明操作數(shù)棧深度最大為1,locals = 1,局部變量表所需要的內(nèi)容為1個(gè)slot。
對(duì)于執(zhí)行引擎來說,活動(dòng)線程中,只有棧頂?shù)臈E是有效的,稱為當(dāng)前棧楨,這個(gè)棧楨所關(guān)聯(lián)的方法稱為當(dāng)前方法,執(zhí)行引擎所運(yùn)行的所有字節(jié)碼指令都只針對(duì)當(dāng)前棧楨進(jìn)行操作。
1、局部變量表
局部變量表是一組變量值存儲(chǔ)空間,用于存放方法參數(shù)和方法內(nèi)部定義的局部變量。
在Java程序被編譯為Class文件時(shí),就在方法的Code屬性的max_locals數(shù)據(jù)項(xiàng)中確定了該方法所需要分配的最大局部變量表的容量。
局部變量表以slot為單位,每個(gè)slot單位可以存放一個(gè)boolean,byte,char,short,int,float,reference或returnAddress類型的數(shù)據(jù)。對(duì)于64位類型的數(shù)據(jù),虛擬機(jī)會(huì)以高位在前的方式為其分配2個(gè)連續(xù)的slot空間。64位的數(shù)據(jù)類型只有兩種:long和double。
虛擬機(jī)通過索引定位方式引用局部變量表,如果是32位類型數(shù)據(jù),索引n就代表使用了第n個(gè)slot,如果是64位,則說明要使用n和n+1兩個(gè)slot
注意,如果是非static方法,局部變量表中的第0位索引的slot,默認(rèn)是用于傳遞方法所屬對(duì)象實(shí)例的引用,即this。
局部變量表中的slot是可以重用的,方法體中的定義的變量作用域不一定會(huì)覆蓋整個(gè)方法體,如果超出作用域,那么這個(gè)變量的slot就可以交給其它變量使用。
2、操作數(shù)棧
操作數(shù)棧也稱為操作棧,它是一個(gè)先入后出的棧。
同局部變量表一樣,操作數(shù)棧的最大深度也在編譯時(shí)候被寫入Code屬性的max_stacks屬性中了。操作數(shù)棧的每一個(gè)元素可以是任意的Java數(shù)據(jù)類型,包括long和double,32位數(shù)據(jù)類型占據(jù)棧容量為1,64位??臻g占位為2,在方法執(zhí)行的任何階段,操作數(shù)棧的深度都不會(huì)超過max_stacks。
當(dāng)一個(gè)方法開始執(zhí)行時(shí),操作數(shù)棧是空的。
舉個(gè)例子說明操作數(shù)棧是如何工作的。整數(shù)加法的字節(jié)碼指令iadd在運(yùn)行的時(shí)候,要求操作數(shù)棧中最接近棧頂?shù)膬蓚€(gè)元素已經(jīng)存入了int值,當(dāng)執(zhí)行此命令時(shí),會(huì)將這兩個(gè)int值出棧并相加,然后將結(jié)果入棧。
Java虛擬機(jī)被稱為“基于棧的執(zhí)行引擎”,其中所指的棧就是操作數(shù)棧。Android虛擬機(jī)則是基于寄存器的虛擬引擎。
3、動(dòng)態(tài)連接
每一個(gè)棧楨中都包含一個(gè)指向運(yùn)行時(shí)常量池中該棧楨所屬方法的引用,持有這個(gè)引用是為了支持方法調(diào)用過程中的動(dòng)態(tài)連接。
Class文件加載過程中,有一個(gè)解析階段,將符號(hào)引用轉(zhuǎn)化為直接引用,但這個(gè)轉(zhuǎn)化不是完全體的,只轉(zhuǎn)化了一部分,還有一部分需要在運(yùn)行時(shí)靠動(dòng)態(tài)連接來完成。
Java三大特性中的多態(tài)就是依靠動(dòng)態(tài)連接完成的,如果在編譯期間就完全轉(zhuǎn)化成直接引用,那多態(tài)就不會(huì)出現(xiàn)了。關(guān)于這點(diǎn)在方法調(diào)用那節(jié)再談。
4、方法返回地址
當(dāng)一個(gè)方法執(zhí)行后,有兩種方式退出方法
- 執(zhí)行引擎遇到任何一個(gè)方法返回的字節(jié)碼指令。
- 方法執(zhí)行過程中遇到了異常,并且異常沒有在方法中得到處理。
無論是何種退出方式,方法退出后,都需要返回到方法被調(diào)用的位置,程序才能繼續(xù)執(zhí)行。調(diào)用者的PC計(jì)數(shù)器的值就可以作為返回地址,棧楨中很可能會(huì)保存這個(gè)計(jì)數(shù)器值。
退出方法過程實(shí)際上等同于把當(dāng)前棧楨出棧,因此退出時(shí)可能執(zhí)行的操作有:恢復(fù)上層方法的局部變量表和操作數(shù)棧,把返回值(如果有)壓入調(diào)用者棧楨的操作數(shù)棧中,調(diào)用PC計(jì)數(shù)器的值以指向方法調(diào)用指令后面的一條指令等。
關(guān)于方法調(diào)用、字節(jié)碼詳細(xì)解析過程,下一篇文章繼續(xù)分析。