"虛擬機(jī)"是相對(duì)于"物理機(jī)"的概念,這兩種機(jī)器都有執(zhí)行代碼能力乍炉,其區(qū)別是物理機(jī)的執(zhí)行引擎是直接建立在處理器绢片、硬件、指令集和操作系統(tǒng)層面上的岛琼,而虛擬機(jī)的執(zhí)行引擎則是由自己實(shí)現(xiàn)的底循,因此可以自行制定指令集與執(zhí)行引擎的結(jié)構(gòu)體系,并且能夠執(zhí)行哪些不被硬件直接支持的指令集格式槐瑞。在不同的虛擬機(jī)實(shí)現(xiàn)里面熙涤,執(zhí)行引擎在執(zhí)行Java代碼的時(shí)候可能會(huì)有解釋執(zhí)行(通過(guò)解釋器執(zhí)行)和編譯執(zhí)行(通過(guò)即時(shí)編譯器產(chǎn)生本地代碼執(zhí)行)兩種選擇,也可能兩者兼?zhèn)洌踔吝€可能會(huì)包含幾個(gè)不同級(jí)別的編譯器執(zhí)行引擎祠挫,但從外觀上看起來(lái)猬错,所有的Java虛擬機(jī)的執(zhí)行引擎都是一致的:輸入的是字節(jié)碼文件,處理過(guò)程是字節(jié)碼解析的等效過(guò)程茸歧,輸出的是執(zhí)行結(jié)果倦炒。
運(yùn)行時(shí)棧幀結(jié)構(gòu)
棧幀(Statck Frame)是用于支持虛擬機(jī)進(jìn)行方法調(diào)用和方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu),它是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)中的虛擬機(jī)棧(Virtual Machine Stack)的棧元素软瞎。棧幀存儲(chǔ)了方法的局部變量表逢唤、操作數(shù)棧、動(dòng)態(tài)連接和方法返回地址等信息涤浇。每一個(gè)方法從調(diào)用開(kāi)始至執(zhí)行完成的過(guò)程鳖藕,都是對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧里面從入棧到出棧的過(guò)程。 一個(gè)線程中的方法調(diào)用鏈可能會(huì)很長(zhǎng)只锭,很多方法都同事對(duì)處于執(zhí)行狀態(tài)著恩。但對(duì)于執(zhí)行引擎來(lái)說(shuō),在活動(dòng)線程中蜻展,只有位于棧頂?shù)臈攀怯行У暮硖埽Q為當(dāng)前棧幀(Current Stack Frame),與這個(gè)棧幀相關(guān)聯(lián)的方法稱為當(dāng)前方法(Current Method)纵顾。?
局部變量表
局部變量表(Local Variable Table)是一組變值存儲(chǔ)空間伍茄,用于存放方法參數(shù)和方法內(nèi)部定義的局部變量。在Java程序編譯為Class文件時(shí)施逾,就在放的Code屬性的max_locals數(shù)據(jù)項(xiàng)中確定了該方法所需要分配的局部變量表的最大容量敷矫。 局部變量表的容量以變量槽(Variable Slot)為最小單位。每個(gè)Slot都應(yīng)該能存放一個(gè)boolean汉额、byte曹仗、char、short蠕搜、int怎茫、float、reference讥脐、或returnAddress類(lèi)型的數(shù)據(jù)遭居,這8種數(shù)據(jù)類(lèi)型,都可以使用32位或更小的物理內(nèi)存來(lái)存放旬渠。Slot的長(zhǎng)度可以隨著處理器俱萍、操作系統(tǒng)或虛擬機(jī)的不同而發(fā)生變化。
reference類(lèi)型
reference類(lèi)型類(lèi)型表示對(duì)一個(gè)對(duì)象實(shí)例的引用告丢。虛擬機(jī)規(guī)范既沒(méi)有說(shuō)明它的長(zhǎng)度枪蘑,也沒(méi)有明確指出這種引用應(yīng)有怎樣的結(jié)構(gòu)。但一般說(shuō)來(lái),虛擬機(jī)實(shí)現(xiàn)至少都是通過(guò)這個(gè)引用做到兩點(diǎn)岳颇,一是從此引用中直接或間接地查到對(duì)象在Java堆中的數(shù)據(jù)存放地址索引照捡,二是引用中直接或間接地查找到對(duì)象所屬數(shù)據(jù)類(lèi)型在方法區(qū)中的存儲(chǔ)的類(lèi)型信息,否則無(wú)法實(shí)現(xiàn)Java語(yǔ)言規(guī)范中定義的語(yǔ)法約束话侧。
64位數(shù)據(jù)類(lèi)型(long栗精、double)
對(duì)于64位的數(shù)據(jù)類(lèi)型,虛擬機(jī)以高位對(duì)齊的方式為其分配兩個(gè)連續(xù)的Slot空間瞻鹏。Java語(yǔ)言中明確的(reference類(lèi)型則可能是32位也可能是64位)64位的數(shù)據(jù)類(lèi)型只有l(wèi)ong和double兩種悲立。由于局部變量表建立在線程的堆棧上,是線程私有的數(shù)據(jù)新博,無(wú)論讀寫(xiě)兩個(gè)連續(xù)的Slot是否為原子操作薪夕,都不會(huì)引起數(shù)據(jù)安全問(wèn)題。
Slot的訪問(wèn)方式
虛擬機(jī)通過(guò)索引定位的方式使用局部變量表赫悄,索引值的范圍從0開(kāi)始至局部變量表最大的Slot數(shù)量原献。如果訪問(wèn)的是32位數(shù)據(jù)類(lèi)型的變量,索引n就代表了使用第幾個(gè)Slot埂淮,如果是64位數(shù)據(jù)類(lèi)型的變量姑隅,則說(shuō)明會(huì)同時(shí)使用n和n+1兩個(gè)Slot。對(duì)于兩個(gè)相鄰的共同存放一個(gè)64位數(shù)據(jù)的兩個(gè)Slot同诫,不允許采用任何方式單獨(dú)訪問(wèn)其中的某一個(gè)粤策。
局部變量表Slot的復(fù)用
在上述代碼中,placeholder能否被回收的根本原因是:局部變量表中的Slot是否還存在有關(guān)于placeholder數(shù)組的引用误窖。第一次修改中,代碼雖然已經(jīng)離開(kāi)了placeholder的作用域秩贰,但在此之后霹俺,沒(méi)有任何對(duì)局部變量表的讀寫(xiě)操作,placeholder原本所占用的Slot還沒(méi)有被其他變量所復(fù)用毒费,所以作為GC Roots一部分的局部變量表仍然保持著對(duì)它的關(guān)聯(lián)丙唧。
關(guān)于局部變量表,還有一點(diǎn)可能對(duì)世紀(jì)開(kāi)發(fā)產(chǎn)生影響觅玻,就是局部變量不像前面介紹的類(lèi)變量那樣存在“準(zhǔn)備階段”想际。類(lèi)變量有兩次賦初始值的過(guò)程,一次在準(zhǔn)備階段溪厘,賦予系統(tǒng)初始值胡本;另一次在初始化階段,賦予程序員定義的初始值畸悬。但局部變量就不一樣侧甫,如果一個(gè)局部變量定義了但沒(méi)有賦初始值是不能使用的。
操作數(shù)棧
操作數(shù)棧(Operand Stack)也常稱為操作棧,它是一個(gè)后入先出(Last In First Out披粟,LIFO)棧咒锻。同局部變量表一樣,操作數(shù)棧的最大深度也在編譯的時(shí)候?qū)懭氲搅薈ode屬性的max_stacks數(shù)據(jù)項(xiàng)中守屉。操作數(shù)棧的每一個(gè)元素可以是任意的Java數(shù)據(jù)類(lèi)型惑艇,包括long和double。32位數(shù)據(jù)類(lèi)型所占的棧容量為1拇泛,64位數(shù)據(jù)類(lèi)型所占的棧容量為2敦捧。在方法執(zhí)行的任何時(shí)候,操作數(shù)棧的深度都不會(huì)超過(guò)在max_stacks數(shù)據(jù)項(xiàng)中設(shè)定的最大值碰镜。
工作原理
當(dāng)一個(gè)方法剛剛開(kāi)始執(zhí)行的時(shí)候兢卵,這個(gè)方法的操作數(shù)棧是空的,在方法的執(zhí)行過(guò)程中绪颖,會(huì)有各種字節(jié)碼指令往操作數(shù)棧中寫(xiě)入和提取內(nèi)容秽荤,也就是出棧/入棧操作。
動(dòng)態(tài)連接
每個(gè)棧幀都包含一個(gè)指向運(yùn)行時(shí)常量池中該棧幀所屬方法的引用柠横,持有這個(gè)引用是為了支持方法調(diào)用過(guò)程中的動(dòng)態(tài)連接(Dynamic Linking)窃款。
方法返回地址
當(dāng)一個(gè)方法開(kāi)始執(zhí)行后,只有兩種方式可以退出這個(gè)方法牍氛。第一種是執(zhí)行引擎遇到任意一個(gè)方法返回的字節(jié)碼指令(_return),這時(shí)候可能會(huì)有返回值傳遞給上層的方法調(diào)用者晨继,是否有返回值和返回值類(lèi)型將根據(jù)遇到何種方法返回指令來(lái)決定,這種退出方法的方式正常完成出口(Normal Method Invocation Completion)
另一種退出方式是搬俊,在方法執(zhí)行過(guò)程中遇到了異常紊扬,并且這個(gè)異常沒(méi)有在方法體內(nèi)得到處理,無(wú)論是Java虛擬機(jī)內(nèi)部產(chǎn)生的異常唉擂,還是代碼中使用athrow字節(jié)碼指令產(chǎn)生的異常餐屎,只要在本方法的異常表中沒(méi)有搜索到匹配的異常處理器,就會(huì)導(dǎo)致方法退出玩祟,這種退出方式稱為異常完成出口(Abrupt Method Invocation Completion)腹缩。