java 参数类型不确定_详细解析Java虚拟机的栈帧结构
什么是棧幀?
正如大家所了解的,Java虛擬機(jī)的內(nèi)存區(qū)域被劃分為程序計數(shù)器、虛擬機(jī)棧、本地方法棧、堆和方法區(qū)。(什么?你還不知道,趕緊去看看《Java虛擬機(jī)內(nèi)存結(jié)構(gòu)及編碼實戰(zhàn)》)這次要介紹的棧幀(Stack Frame),就是Java虛擬機(jī)中的虛擬機(jī)棧(Virtual Machine Stack)的基本元素,它也是用于支持Java虛擬機(jī)進(jìn)行方法調(diào)用和方法執(zhí)行背后的數(shù)據(jù)結(jié)構(gòu),了解了它就可以更好地理解Java虛擬機(jī)執(zhí)行引擎是如何運行的。
每一個方法從調(diào)用開始至執(zhí)行結(jié)束的整個過程,都對應(yīng)著一個棧幀在虛擬機(jī)棧中從入棧到出棧的過程。棧幀存儲了方法的局部變量表、操作數(shù)棧、動態(tài)連接和方法返回地址等信息,在同一時刻、同一條線程中,只有位于棧頂?shù)姆椒ú攀窃谶\行的,只有位于棧頂?shù)臈攀巧У?#xff0c;執(zhí)行引擎所運行的所有字節(jié)碼指令都只針對當(dāng)前棧幀進(jìn)行操作。虛擬機(jī)棧和棧幀的總體結(jié)構(gòu)如下圖:
接下來,再分別介紹一下棧幀中的局部變量表、操作數(shù)棧、動態(tài)連接、方法返回地址等各個部分的作用和數(shù)據(jù)結(jié)構(gòu)。
局部變量表(Local Variables Table)
局部變量表是用來存儲一組變量值的內(nèi)存空間,用于存放方法參數(shù)和方法內(nèi)部定義的局部變量。在已經(jīng)編譯好的Class文件中,方法的Code屬性的max_locals數(shù)據(jù)項中,就確定了該方法所需分配的局部變量表的最大容量。
局部變量表的容量以變量槽(Variable Slot)為最小單位,每個變量槽存放一個32位數(shù)據(jù)類型,如boolean、byte、char、short、int、float和reference這幾種類型。前6種類型同學(xué)們應(yīng)該都了解,就不必多介紹了,reference類型表示對一個對象實例的引用,通過這個引用做到兩件事情:根據(jù)引用直接或間接地查找到實例在Java堆中的數(shù)據(jù)存放的起始地或索引;根據(jù)引用直接或間接地查找到在方法區(qū)中的存儲的類信息。對于64位數(shù)據(jù)類型,如long和double這兩種類型,是以高位對齊的方式為其分配兩個連續(xù)的變量槽空間。
使用局部變量表時,通過索引定位對應(yīng)數(shù)據(jù)的位置,索引值的范圍是從0開始至局部變量表最大的變量槽數(shù)量。如果訪問的是32位數(shù)據(jù)類型的變量,索引N就代表了使用第N個變量槽,如果訪問的是64位數(shù)據(jù)類型的變量,則說明會同時使用第N和N+1兩個變量槽。對于兩個相鄰的共同存放一個64位數(shù)據(jù)的兩個變量槽,虛擬機(jī)不允許采用任何方式單獨訪問其中的某一個,如果遇到進(jìn)行這種操作的字節(jié)碼,Java虛擬機(jī)就會在類加載的校驗階段中拋出異常。
當(dāng)一個方法被調(diào)用時,會使用局部變量表來完成參數(shù)值到參數(shù)變量列表的傳遞過程。如果執(zhí)行的是對象實例的成員方法(沒有被static修飾的方法),那么局部變量表中第0位索引的變量槽默認(rèn)就是該對象實例的引用,在方法中可以通過關(guān)鍵字this來訪問到這個隱含的參數(shù)。其余參數(shù)則按照參數(shù)表順序排列,參數(shù)表分配完畢后,再根據(jù)方法體內(nèi)部定義的局部變量順序和作用域分配其余的變量槽。為了盡可能節(jié)省棧幀所耗的內(nèi)存空間,局部變量表中的變量槽是可以重用的,當(dāng)方法體中定義的局部變量超出其作用域時,該局部變量對應(yīng)的變量槽就可以交給其他變量來重用。
之前的《JVM的類加載機(jī)制詳解》中介紹過,在類加載過程中,類變量有兩次賦初始值的過程,一次在準(zhǔn)備階段,賦予系統(tǒng)初始值;另外一次在初始化階段,賦予代碼中定義的初始值。因此即使沒有為類變量賦值也沒有關(guān)系,類變量仍然具有一個確定的初始值,不會產(chǎn)生歧義。但是局部變量不像類變量有那樣的“準(zhǔn)備階段”,如果一個局部變量定義了但沒有賦初始值,那它是完全不能使用的。所以不要認(rèn)為Java中任何情況下都存在諸如整型變量默認(rèn)為0、布爾型變量默認(rèn)為false等這樣的默認(rèn)值規(guī)則。比如:
public class OneMoreStudy {public static void main(String[] args) {int i;System.out.println(i);}
}因為局部變量i沒有初始,在編譯過程就會報錯:
Error:(4, 28) java: 可能尚未初始化變量i操作數(shù)棧(Operand Stack)
操作數(shù)棧是一個后入先出(Last In First Out,LIFO)棧。和局部變量表一樣,在已經(jīng)編譯好的Class文件中,方法的Code屬性的max_stacks數(shù)據(jù)項中,就確定了該方法所需分配的操作數(shù)棧的最大深度。在方法執(zhí)行的任何時候,操作數(shù)棧的深度都不會超過在max_stacks數(shù)據(jù)項中設(shè)定的最大值。操作數(shù)棧的每一個元素都可以是包括long和double在內(nèi)的任意Java數(shù)據(jù)類型。32位數(shù)據(jù)類型所占的棧容量為1,64位數(shù)據(jù)類型所占的棧容量為2。
當(dāng)一個方法剛剛開始執(zhí)行的時候,該方法的操作數(shù)棧是空的,在該方法的執(zhí)行過程中,會有各種字節(jié)碼指令對操作數(shù)棧進(jìn)行出棧和入棧的操作。比如,整數(shù)加法的字節(jié)碼指令iadd,在該指令執(zhí)行前必須保證操作數(shù)棧中最接近棧頂?shù)膬蓚€元素已經(jīng)存入了兩個int型的數(shù)值,當(dāng)該指令執(zhí)行時,會把這兩個int值出棧并相加,然后將相加的結(jié)果重新入棧。
操作數(shù)棧中元素的數(shù)據(jù)類型必須與字節(jié)碼指令的序列嚴(yán)格匹配,在編譯代碼時,編譯器會嚴(yán)格保證這一點,在類加載的校驗階段也會再次驗證這一點。在上面的iadd指令中,只能用于整型數(shù)的加法,它在執(zhí)行時,最接近棧頂?shù)膬蓚€元素的數(shù)據(jù)類型必須為int型,不能出現(xiàn)其他數(shù)據(jù)類型使用iadd命令相加的情況。
一個方法調(diào)用另外一個方法時,可以通過操作數(shù)棧來進(jìn)行方法參數(shù)的傳遞。雖然在Java虛擬機(jī)規(guī)范中,兩個不同棧幀作為不同方法的虛擬機(jī)棧的元素,是完全相互獨立的。但是在大多Java虛擬機(jī)的實現(xiàn)時,都會進(jìn)行一些優(yōu)化:兩個不同方法的棧幀出現(xiàn)一部分重疊。讓下面棧幀的部分操作數(shù)棧與上面棧幀的部分局部變量表重疊在一起,這樣做不僅節(jié)約了一些內(nèi)存空間,更重要的是在進(jìn)行方法調(diào)用時就可以直接共用一部分?jǐn)?shù)據(jù),不需要進(jìn)行額外的參數(shù)復(fù)制和傳遞,如下圖:
動態(tài)連接(Dynamic Linking)
每個棧幀都包含一個指向運行時常量池中該棧幀所屬方法的引用,持有這個引用是為了支持方法調(diào)用過程中的動態(tài)連接。
之前的《Class文件結(jié)構(gòu)全面解析》中介紹過,Class文件的常量池中存有大量的符號引用,這些符號引用一部分會在類加載階段或者第一次使用的時候就被轉(zhuǎn)化為直接引用(實際運行時內(nèi)存布局中的入口地址),這種轉(zhuǎn)化被稱為靜態(tài)解析。另外一部分將在每一次運行期間都轉(zhuǎn)化為直接引用,這部分就稱為動態(tài)連接。關(guān)于這兩個轉(zhuǎn)化過程的具體過程,這里先賣個關(guān)子,后續(xù)的文章會詳細(xì)介紹。
方法返回地址
方法返回時可能需要在棧幀中保存一些信息,用來于恢復(fù)調(diào)用者(調(diào)用當(dāng)前方法的方法)的執(zhí)行狀態(tài)。一般來說,方法正常退出時,調(diào)用者的程序計數(shù)器的值就可以作為返回地址,棧幀中很可能會保存這個計數(shù)器值。而方法異常退出時,返回地址是要通過異常處理器表來確定的,棧幀中就一般不會保存這部分信息。
方法返回的過程實際上等同于把當(dāng)前棧幀出棧,可能執(zhí)行的操作有:恢復(fù)調(diào)用者的局部變量表和操作數(shù)棧,把返回值(如果有的話)壓入調(diào)用者棧幀的操作數(shù)棧中,調(diào)整程序計數(shù)器的值使其指向方法調(diào)用指令后面的一條指令等等。
附加信息
在Java虛擬機(jī)規(guī)范中,允許Java虛擬機(jī)增加一些規(guī)范里沒有描述的信息到棧幀之中,比如:調(diào)試、性能收集相關(guān)的信息,這部分信息完全取決于具體的虛擬機(jī)實現(xiàn)。一般會把動態(tài)連接、方法返回地址和其他附加信息全部歸為一類,稱為棧幀信息。
總結(jié)
棧幀是Java虛擬機(jī)中的虛擬機(jī)棧的基本元素,每一個方法從調(diào)用開始至執(zhí)行結(jié)束的整個過程,都對應(yīng)著一個棧幀在虛擬機(jī)棧中從入棧到出棧的過程。棧幀存儲了方法的局部變量表、操作數(shù)棧、動態(tài)連接和方法返回地址和其他附加信息。局部變量表用于存放方法參數(shù)和方法內(nèi)部定義的局部變量;各種字節(jié)碼指令執(zhí)行時,會對操作數(shù)棧進(jìn)行出棧和入棧的操作;動態(tài)連接是指向運行時常量池中該棧幀所屬方法的引用;方法返回地址用于恢復(fù)調(diào)用當(dāng)前方法的方法的執(zhí)行狀態(tài)。
歡迎關(guān)注微信公眾號:萬貓學(xué)社,每周一分享Java技術(shù)干貨。
總結(jié)
以上是生活随笔為你收集整理的java 参数类型不确定_详细解析Java虚拟机的栈帧结构的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: DNF太阳印章有什么用?
- 下一篇: qq个性男生网名大全