運(yùn)行時棧幀結(jié)構(gòu)
棧幀(Stack Frame)是用于虛擬機(jī)進(jìn)行方法調(diào)用和方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu)低匙,它是虛擬機(jī)運(yùn)行時數(shù)據(jù)區(qū)中的虛擬機(jī)棧(Virtual Machine Stack)的棧元素恩沽。棧幀存儲了方法的局部變量表葛碧、操作數(shù)棧糯钙、動態(tài)連接和方法返回地址等信息。每一個方法從調(diào)用開始到執(zhí)行完成的過程挂洛,都對應(yīng)著一個棧幀從入棧到出棧的過程礼预。
1、局部變量表
局部變量表(Local Variable Table)是一組變量值存儲空間虏劲,用于存放方法參數(shù)和方法內(nèi)部定義的局部變量托酸。在Java程序編譯為Class文件時,就在方法的Code屬性的max_locals數(shù)據(jù)項中確定了該方法需要分配的局部變量表的最大容量柒巫。
在方法執(zhí)行時励堡,虛擬機(jī)是使用局部變量表完成參數(shù)值到參數(shù)列表的傳遞過程,如果執(zhí)行的是實例方法(非static方法)堡掏,那局部變量表中第0位索引的Slot默認(rèn)是用于傳遞方法所屬對象實例的引用应结,在方法中可以通過關(guān)鍵字“this”來訪問到這個隱含的參數(shù)。其余參數(shù)則按照參數(shù)表順序排列泉唁,占用從1開始的局部變量Slot鹅龄,參數(shù)表分配完成后,再根據(jù)方法體內(nèi)部定義的變量順序和作用域分配其余的Slot亭畜。
為了盡可能節(jié)省棧幀空間扮休,局部變量表中的Slot是可以重用的,方法體中定義的變量拴鸵,其作用域并不一定會覆蓋整個方法體玷坠,如果當(dāng)前字節(jié)碼PC計數(shù)器的值已經(jīng)超出了某個變量的作用域,那這個變量對應(yīng)的Slot就可以交給其他變量使用劲藐。某些情況下八堡,Slot復(fù)用會影響到垃圾收集行為。
2聘芜、操作數(shù)棧
操作數(shù)棧(Operand Stack)也稱為操作棧兄渺,它是一個后入先出(Last In First Out,LIFO)棧。同局部變量表一樣汰现,操作數(shù)棧的最大深度也在編譯的時候?qū)懭氲紺ode屬性的max_stacks數(shù)據(jù)項中挂谍。操作數(shù)棧的每一個元素可以是任意的Java數(shù)據(jù)類型,包括long和double服鹅。
在方法的執(zhí)行過程中凳兵,會有各種字節(jié)碼指令往操作數(shù)棧中寫入和提取內(nèi)容百新,也就是出棧/入棧操作企软。
在概念模型中,兩個棧幀作為虛擬機(jī)棧的元素饭望,是完全相互獨立的仗哨。但在大多數(shù)虛擬機(jī)的實現(xiàn)里都會做一些優(yōu)化處理形庭,令兩個棧幀出現(xiàn)一部分重疊。讓下面棧幀的部分操作數(shù)棧與上面棧幀的局部變量表重疊到一起厌漂,這樣在進(jìn)行方法調(diào)用時就可以共用一部分?jǐn)?shù)據(jù)萨醒,無需進(jìn)行額外的參數(shù)復(fù)制傳遞。
3苇倡、動態(tài)連接
每個棧幀都包含一個指向運(yùn)行時常量池中該棧幀所屬方法的引用富纸,持有這個引用是為了支持方法調(diào)用過程中的動態(tài)連接。
4旨椒、方法返回地址
當(dāng)一個方法開始執(zhí)行后晓褪,只有兩種方式可以退出這個方法。
1综慎、執(zhí)行引擎遇到任意一個方法返回的字節(jié)碼指令涣仿,這時候可能會有返回值傳遞給上層的方法調(diào)用者,是否有返回值和返回值的類型將根據(jù)遇到何種方法返回指令來決定示惊,這種退出方法的方式稱為正常完成出口好港。
2、另一種退出方式是米罚,在方法執(zhí)行過程中遇到了異常钧汹,并且這個異常沒有在方法體內(nèi)得到處理,無論是Java虛擬機(jī)內(nèi)部產(chǎn)生的異常阔拳,還是代碼中使用athrow字節(jié)碼指令產(chǎn)生的異常崭孤,只要在方法的異常表中沒有搜索到匹配的異常處理器,就會導(dǎo)致方法退出糊肠,這種退出方式稱為異常方法出口辨宠。
5、附加信息
虛擬機(jī)規(guī)范允許具體的虛擬機(jī)實現(xiàn)增加一些規(guī)范里沒有描述的信息到虛擬機(jī)棧幀之中货裹。
方法調(diào)用
方法調(diào)用并不等同于方法執(zhí)行嗤形,方法調(diào)用階段唯一的任務(wù)就是確定被調(diào)用方法的版本,暫時還不涉及方法內(nèi)部的具體運(yùn)行過程弧圆。
1赋兵、解析
所有方法調(diào)用中的目標(biāo)方法在Class文件里面都是一個常量池中的符號引用,在類加載的解析階段搔预,會將其中的一部分符號引用轉(zhuǎn)化為直接引用霹期,這種解析能成立的前提是:方法在程序運(yùn)行之前就有一個可確定的調(diào)用版本,并且這個方法的調(diào)用版本在運(yùn)行期間不會改變拯田。
在Java語言中符合“編譯期可知历造,運(yùn)行期不可變”這個要求的方法,主要包括靜態(tài)方法和私有方法兩大類,前者與類型直接關(guān)聯(lián)吭产,后者在外部不可訪問侣监,這兩種方法各自的特點決定了它們都不可能通過繼承或別的方式重寫其他版本,因此它們都適合在類加載階段進(jìn)行解析臣淤。
與之對應(yīng)橄霉,Java虛擬機(jī)里提供了5條方法調(diào)用字節(jié)碼指令。
invokestatic:調(diào)用靜態(tài)方法
invokespecial:調(diào)動實例構(gòu)造器<init>方法邑蒋、私有方法和父類方法
invokevirtual:調(diào)用所有的虛方法
invokeinterface:調(diào)用接口方法姓蜂,會在運(yùn)行時再確定一個實現(xiàn)此接口的對象
invokedynamic:先在運(yùn)行時動態(tài)解析出調(diào)用點限定符所引用的方法,然后再執(zhí)行該方法医吊,在此之前的4條調(diào)用指令覆糟,分派邏輯是固化在Java虛擬機(jī)內(nèi)部的,而invokedynamic指令的分派邏輯是由用戶所設(shè)定的引導(dǎo)方法決定的遮咖。
2滩字、分派
Java是一門面向?qū)ο蟮某绦蛘Z言,因為Java具備面向?qū)ο蟮?個基本特征:繼承御吞、封裝和多態(tài)麦箍。分派調(diào)用將會揭示多態(tài)特征的一些最基本體現(xiàn),如“重載”和“重寫”在Java虛擬機(jī)中是如何實現(xiàn)的陶珠。
2.1挟裂、靜態(tài)分派
重載
2.2、動態(tài)分派
invokevirtual指令運(yùn)行時解析過程大致分為以下步驟:
1揍诽、找到操作數(shù)棧頂?shù)牡谝粋€元素所指向的對象的實際類型诀蓉,記作C。
2暑脆、如果在類型C中找到與常量中的描述符和簡單名稱都相符的方法渠啤,則進(jìn)行訪問權(quán)限校驗,如果通過則返回這個方法的直接引用添吗,查找過程結(jié)束沥曹;如果不通過,則返回java.lang.IllegalAccessError異常碟联。
3妓美、否則,按照繼承關(guān)系從下往上依次對C的各個父類進(jìn)行第2步的搜索和驗證過程鲤孵。
4壶栋、如果始終沒有找到合適的方法,則拋出java.lang.AbstractMethodError異常普监。
由于invokevirtual指令執(zhí)行的第一步就是在運(yùn)行期確定接受者的實際類型贵试,所以兩次調(diào)用中的invokevirtual指令把常量池中的類方法符號引用解析到了不同的直接引用上丧没,這個過程就是Java語言中方法重寫的本質(zhì)。我們把運(yùn)行期間根據(jù)實際類型確定方法執(zhí)行版本的過程稱為動態(tài)分派锡移。
2.3、單分派與多分派
2.4漆际、虛擬機(jī)動態(tài)分派的實現(xiàn)
3淆珊、動態(tài)類型語言支持
3.1、動態(tài)類型語言
3.2奸汇、JDK1.7與動態(tài)類型
3.3施符、java.lang.invoke包
3.4、invokedynamic指令
3.5擂找、掌握方法分派規(guī)則
4戳吝、基于棧的字節(jié)碼解釋執(zhí)行引擎
4.1、解釋執(zhí)行
4.2贯涎、基于棧的指令集與基于寄存器的指令集
4.3听哭、基于棧的解釋器執(zhí)行過程
摘自 《深入理解Java虛擬機(jī)》