1 運(yùn)行期的數(shù)據(jù)區(qū)域
jvm定義了各種運(yùn)行期的數(shù)據(jù)區(qū)域,可以在執(zhí)行程序時(shí)使用。有些數(shù)據(jù)區(qū)域在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建,當(dāng)虛擬機(jī)中止時(shí)才被銷毀。另外一些數(shù)據(jù)區(qū)域是單個(gè)線程的,單個(gè)線程的數(shù)據(jù)區(qū)域在線程被創(chuàng)建時(shí)創(chuàng)建,線程中止時(shí)銷毀。
1.1 pc寄存器(pc register)
jvm能夠支持多個(gè)線程同時(shí)執(zhí)行。每個(gè)java虛擬機(jī)線程都有獨(dú)自的pc寄存器(程序計(jì)數(shù)器)。在任何時(shí)候,每個(gè)jvm線程執(zhí)行單個(gè)方法的代碼時(shí),這個(gè)方法就是那個(gè)線程的當(dāng)前方法。如果那個(gè)方法不是本地的(native),pc寄存器包含當(dāng)前正在被執(zhí)行的指令地址。如果線程正在執(zhí)行的方法是本地的(native),jvm的pc寄存器是未定義的。jvm的pc寄存器有足夠的寬度來(lái)持有一個(gè)返回地址(returnAddress)或在特定的平臺(tái)上的本地指針(native point)。
1.2 java虛擬機(jī)棧(stacks)
每個(gè)jvm線程都有一個(gè)私有的jvm棧(stack),它將和線程同時(shí)創(chuàng)建。jvm棧存儲(chǔ)幀(frames)。jvm棧類似于傳統(tǒng)語(yǔ)言例如c的棧,它持有局部變量和部分結(jié)果并且參與方法的調(diào)用和返回。 由于jvm棧除了壓入彈出幀外不會(huì)被直接操作,所以幀可以由堆(heap)來(lái)分配。對(duì)于jvm棧的內(nèi)存不必是連續(xù)的。
jvm規(guī)范允許jvm棧的大小是固定的,也可以是根據(jù)需求計(jì)算來(lái)擴(kuò)展和收縮。如果jvm棧是固定大小,則每個(gè)jvm棧大小可以在棧創(chuàng)建時(shí)獨(dú)立地選擇。一個(gè)jvm實(shí)現(xiàn)可以讓程序員或用戶控制jvm初始棧的大小,以及在動(dòng)態(tài)擴(kuò)展或收縮jvm棧時(shí),控制其最大值和最小值。
以下異常情況與jvm棧有關(guān):
- 如果線程中的計(jì)算需要一個(gè)比允許的jvm棧更大時(shí),jvm將會(huì)拋出StackOverflowError.
- 如果jvm??蓜?dòng)態(tài)擴(kuò)展,當(dāng)沒(méi)有足夠的內(nèi)存分配給所嘗試的擴(kuò)展,或者沒(méi)有足夠的內(nèi)存來(lái)為一個(gè)新線程創(chuàng)建初始化jvm棧,jvm將會(huì)拋出OutOfMemoryError.
1.3 堆(heap)
jvm有一個(gè)所有jvm線程間共享的堆(heap)。堆是分配所有類實(shí)例和數(shù)組內(nèi)存的運(yùn)行期數(shù)據(jù)區(qū)域。
堆在虛擬機(jī)啟動(dòng)時(shí)被創(chuàng)建。堆中對(duì)象的存儲(chǔ)由自動(dòng)存儲(chǔ)管理系統(tǒng)(被稱為垃圾回收器gc)回收。對(duì)象從來(lái)不會(huì)被顯示的回收。jvm承擔(dān)著非特殊類型的自動(dòng)存儲(chǔ)管理系統(tǒng),當(dāng)然存儲(chǔ)管理技術(shù)也可以根據(jù)實(shí)現(xiàn)者的系統(tǒng)要求來(lái)選擇。堆可以是固定大小或是根據(jù)需求計(jì)算進(jìn)行擴(kuò)展,或者也可以是當(dāng)一個(gè)大的堆不必要時(shí)進(jìn)行收縮。堆的內(nèi)存不需要是連續(xù)的。
一個(gè)jvm實(shí)現(xiàn)可以讓開(kāi)發(fā)者或者用戶控制堆初始的大小,同樣的,如果堆能夠動(dòng)態(tài)擴(kuò)展或者收縮,可以控制其最大值和最小值。
以下異常情況與堆有關(guān):
- 如果計(jì)算需求所須更多的堆無(wú)法由自動(dòng)存儲(chǔ)管理系統(tǒng)提供時(shí),jvm將會(huì)拋出OutOfMemoryError.
1.4 方法區(qū)(method area)
jvm有一個(gè)所有jvm線程共享的方法區(qū)。方法區(qū)類似傳統(tǒng)語(yǔ)言編譯后代碼的存儲(chǔ)區(qū),或者像是UNIX進(jìn)程中的正文(text)段。它保存每個(gè)類結(jié)構(gòu)諸如運(yùn)行時(shí)的常量池、域、方法數(shù)據(jù)、方法和構(gòu)造器的代碼(包括在類、實(shí)例初始化、接口初始化時(shí)使用的特殊方法)。
方法區(qū)在虛擬機(jī)啟動(dòng)時(shí)被創(chuàng)建。雖然方法區(qū)邏輯上是堆的一部分,但是簡(jiǎn)單的實(shí)現(xiàn)可以選擇既不垃圾回收也不壓縮它。該版本的jvm規(guī)范不要求指定方法區(qū)的位置或者用于管理編譯后代碼的策略。方法區(qū)可以是固定大小,也可以根據(jù)需求計(jì)算擴(kuò)展,并且當(dāng)大的方法區(qū)不再需要時(shí)進(jìn)行收縮。方法區(qū)的內(nèi)存不需要是連續(xù)的。
一個(gè)jvm實(shí)現(xiàn)可以讓開(kāi)發(fā)者或用戶控制方法區(qū)初始的大小,同樣的,在可變大小方法區(qū)時(shí),控制方法區(qū)的最大值和最小值。
以下異常情況與方法區(qū)有關(guān):
- 如果方法區(qū)的內(nèi)存不能滿足分配求情,jvm會(huì)拋出OutOfMemoryError。
1.5 運(yùn)行常數(shù)池(Runtime Constant Pool)
運(yùn)行常數(shù)池是每個(gè)類或者接口運(yùn)行時(shí)constant_pool表在類文件(class file)中的表現(xiàn)。它包含幾種常數(shù)類型,范圍從編譯期已知的數(shù)字文字到必須在運(yùn)行期被解析的方法和域引用。常數(shù)池的作用類似于傳統(tǒng)編程語(yǔ)言中的符號(hào)表,盡管它比典型符號(hào)表有更寬的數(shù)據(jù)范圍。
每個(gè)常量池由jvm方法區(qū)分配。每個(gè)類或接口的常量池在類和接口被jvm創(chuàng)建時(shí)建立。
以下異常情況與類或接口的常量池有關(guān):
- 當(dāng)創(chuàng)建類或接口時(shí),如果常量池的建立需要的內(nèi)存不能被jvm的方法區(qū)分配,jvm會(huì)拋出OutOfMenoryError.
1.6 本地方法棧(Native Method Stacks)
一個(gè)jvm的實(shí)現(xiàn)可以使用傳統(tǒng)棧,俗稱“C棧(C stacks)”,來(lái)支持本地方法,那些非java編程語(yǔ)言中的的方法。本地方法??梢员籮vm指令集的解釋器實(shí)現(xiàn)(比如C)所使用。不裝載本地方法和不自身依賴傳統(tǒng)棧的jvm實(shí)現(xiàn)者,不需要提供本地方法棧。如果提供,本地方法棧通常由每個(gè)線程在創(chuàng)建時(shí)被分配。
jvm規(guī)范允許本地方法棧是固定大小或者根據(jù)需求動(dòng)態(tài)擴(kuò)展或收縮。如果本地方法是固定大小,每個(gè)本地方法棧大小在棧被創(chuàng)建時(shí)獨(dú)自選擇。在任何情況下,一個(gè)jvm實(shí)現(xiàn)可以讓開(kāi)發(fā)者或用戶控制本地方法棧的初始大小。如果是可變大小本地方法棧,同樣能夠控制方法棧的最大值和最小值。
以下異常情況與本地方法棧有關(guān):
- 如果線程中計(jì)算所需的本地方法棧大于允許范圍,jvm會(huì)拋出StackOverflowError。
- 如果本地方法棧能動(dòng)態(tài)擴(kuò)展,當(dāng)沒(méi)有足夠的內(nèi)存分配給所嘗試的擴(kuò)展,或者沒(méi)有足夠的內(nèi)存分配給新線程中創(chuàng)建的初始本地方法棧,jvm就會(huì)拋出OutOfMemoryError。
2 幀(Frames)
幀用來(lái)存儲(chǔ)數(shù)據(jù)和部分結(jié)果,同樣也用來(lái)執(zhí)行動(dòng)態(tài)鏈接、返回方法值和投遞異常。
每次調(diào)用方法時(shí)會(huì)創(chuàng)建一個(gè)新的幀。當(dāng)方法調(diào)用完成時(shí)(無(wú)論正常或者異常中斷拋出異常),幀才會(huì)銷毀。幀由創(chuàng)建幀的線程的jvm棧分配空間。每個(gè)幀擁有自己的本地變量數(shù)組,操作數(shù)棧(operand stack)和當(dāng)前類中方法運(yùn)行常數(shù)池的引用。
局部變量數(shù)組和操作數(shù)棧的大小在編譯時(shí)決定,并且該大小由與方法代碼相關(guān)聯(lián)的幀提供。因此幀數(shù)據(jù)結(jié)構(gòu)的大小依賴于jvm的實(shí)現(xiàn),幀內(nèi)存在方法調(diào)用時(shí)被同時(shí)分配。
只有一個(gè)幀,即正在執(zhí)行方法的幀,在給定線程控制的任何點(diǎn)是活躍的。這個(gè)幀被稱為當(dāng)前幀,這個(gè)方法即為當(dāng)前方法。當(dāng)前方法所在的類被定義為當(dāng)前類。局部變量和操作數(shù)棧的操作通常引用當(dāng)前幀。
只有當(dāng)方法調(diào)用另一個(gè)方法或者方法結(jié)束時(shí),幀不再為當(dāng)前幀。當(dāng)一個(gè)方法被調(diào)用,控制轉(zhuǎn)移到新方法時(shí),一個(gè)新幀被創(chuàng)建并成為當(dāng)前幀。當(dāng)方法返回時(shí),如果方法引用有結(jié)果,則當(dāng)前幀傳遞回該方法引用的結(jié)果給上一個(gè)幀。當(dāng)前幀被拋棄,上一個(gè)幀成為當(dāng)前幀。
注意一個(gè)線程中創(chuàng)建的幀是局部的,不能被其他線程所引用。
2.1局部變量(Local Variables)
每個(gè)幀都有一個(gè)局部變量數(shù)組。幀中局部變量數(shù)組的大小在編譯時(shí)決定,由二進(jìn)制表示的類或接口與幀相關(guān)聯(lián)方法的代碼提供。
一個(gè)單字節(jié)的局部變量能保存boolean、byte、char、short、int、float、引用或返回地址的值。兩個(gè)局部變量能保存long或者double。
局部變量通過(guò)索引尋址。第一個(gè)局部變量的索引為0。本地變量數(shù)組的索引基于0與數(shù)組大小之間的整數(shù)。
long與double類型占據(jù)兩個(gè)連續(xù)的局部變量。該值通過(guò)第一個(gè)變量的索引尋址。例如,一個(gè)double類型的值存儲(chǔ)在局部變量索引為n(其實(shí)占據(jù)了局部變量n和n+1的索引),但本地變量索引n+1不能被使用。它可以存儲(chǔ)到,但是,這樣會(huì)使本地變量n的內(nèi)容無(wú)效。
jvm不需要n為偶數(shù)。在直觀上來(lái)講,類型double和long在局部變量數(shù)組中不需要是64位對(duì)齊的,實(shí)現(xiàn)者可以自由決定用合適的方法諸如用兩個(gè)局部變量來(lái)存儲(chǔ)該值。
jvm通過(guò)局部變量來(lái)傳遞方法引用中的參數(shù)。一個(gè)類方法引用中的任何參數(shù)通過(guò)連續(xù)的局部變量來(lái)傳遞(從局部變量0開(kāi)始)。在一個(gè)實(shí)例方法引用中,局部變量0通常用來(lái)傳遞調(diào)用該方法對(duì)象實(shí)例的引用。其后任何參數(shù)通過(guò)從局部變量1開(kāi)始的連續(xù)局部變量傳遞。
2.2 操作數(shù)棧(Operand Stacks)
每個(gè)幀都有一個(gè)后進(jìn)先出(LIFO)棧,被稱為操作數(shù)棧。一個(gè)幀操作數(shù)棧的最大深度在編譯期決定,由與幀相關(guān)聯(lián)方法的代碼提供。
如果上下文明確,我們會(huì)把當(dāng)前幀的操作數(shù)棧簡(jiǎn)稱為操作數(shù)棧。
當(dāng)幀創(chuàng)建時(shí),其中的操作數(shù)棧是空的。通過(guò)jvm提供的指令加載常數(shù)、局部變量的值或域到操作數(shù)棧。操作數(shù)棧同時(shí)也用來(lái)準(zhǔn)備參數(shù)傳遞給方法,接收方法的返回值。
例如,iadd指令將兩個(gè)整數(shù)值相加。它要求相加的兩個(gè)整數(shù)值是操作數(shù)棧頂?shù)膬蓚€(gè)值(由以前的指令壓入在那里)。兩個(gè)整數(shù)值都從操作數(shù)棧彈出,他們相加,相加的和壓回操作數(shù)棧。子計(jì)算可以嵌套在操作數(shù)棧上,其結(jié)果值可以被相鄰的計(jì)算使用。
每個(gè)操作數(shù)棧項(xiàng)可保存任何jvm類型的值,包括long和double。
操作數(shù)棧的值必須用與他們的值類型相適當(dāng)?shù)姆椒▉?lái)操作。例如,壓入兩個(gè)int值把它們當(dāng)作long來(lái)處理或者壓入兩個(gè)float值隨后通過(guò)iadd指令將他們相加,是不可能的。一小部分jvm指令(dup和swap)在運(yùn)行數(shù)據(jù)區(qū)域操作作為原始值而不需要關(guān)心他們的類型,這些指令被定義不能用來(lái)修改或打斷單獨(dú)的值。class文件校驗(yàn)器加強(qiáng)了這些操作數(shù)棧操作的限制。
在任何時(shí)間點(diǎn)操作數(shù)棧有一個(gè)關(guān)聯(lián)的深度,long和double類型深度為兩個(gè)單位,其他類型深度為一個(gè)單位。
2.3 動(dòng)態(tài)鏈接(Dynamic Linking)
每個(gè)幀都有一個(gè)當(dāng)前方法類型的運(yùn)行常數(shù)池的引用,用來(lái)支持方法代碼的動(dòng)態(tài)鏈接。方法的class文件代碼與被調(diào)用的方法相關(guān)聯(lián),通過(guò)符號(hào)引用來(lái)訪問(wèn)變量。動(dòng)態(tài)鏈接轉(zhuǎn)譯這些符號(hào)方法引用到具體的方法引用,在需要時(shí)加載class來(lái)解析未定義的符號(hào),并且把變量訪問(wèn)轉(zhuǎn)譯成這些變量地址相關(guān)聯(lián)的存儲(chǔ)結(jié)構(gòu)中合適的位移。
這種方法和變量的晚綁定在使用方法時(shí)引起的其他類中的改變而破壞代碼的可能性很小。
2.4 正常的方法調(diào)用結(jié)束
方法調(diào)用在沒(méi)有引起異常拋出時(shí)正常的結(jié)束(異常直接由jvm,或者執(zhí)行顯示throw語(yǔ)句拋出)。如果當(dāng)前方法調(diào)用正常的結(jié)束,則一個(gè)值可以返回到正在調(diào)用該方法的方法。這在調(diào)用的方法執(zhí)行return指令時(shí)發(fā)生,指令返回的選擇必須與被返回值的類型(如果有)相合適。
如下情況幀用來(lái)恢復(fù)調(diào)用者的狀態(tài)(包括局部變量和操作數(shù)棧),當(dāng)調(diào)用者的程序計(jì)數(shù)器適當(dāng)?shù)脑黾犹^(guò)了方法調(diào)用指令。然后返回值(如果有)壓入調(diào)用方法幀的操作數(shù)棧中,執(zhí)行在這個(gè)幀中正常進(jìn)行。
2.5 意外的方法調(diào)用結(jié)束
當(dāng)方法中jvm指令執(zhí)行引起jvm拋出異常,而且異常沒(méi)有被方法處理,這個(gè)方法調(diào)用意外結(jié)束。執(zhí)行throw語(yǔ)句顯示的拋出異常,并且沒(méi)有被當(dāng)前方法捕獲,會(huì)引起方法意外結(jié)束。意外結(jié)束的方法不會(huì)返回任何值給調(diào)用者。
圖.1 運(yùn)行期的數(shù)據(jù)區(qū)域
3 hello world 示例
下面是hello world代碼執(zhí)行時(shí)數(shù)據(jù)區(qū)域的情況。
圖.2 執(zhí)行println方法前
jvm棧中frame a為當(dāng)前幀,該幀為類方法main的幀。操作數(shù)棧頂部指向Hello, world字符串,接下來(lái)那個(gè)位指向System.out的對(duì)象。局部變量指向main方法中的args參數(shù)。程序計(jì)數(shù)器指向main方法中調(diào)用的println指令invokevirtual。
圖.3 執(zhí)行println方法時(shí)
當(dāng)執(zhí)行println方法時(shí),frame a中操作數(shù)彈出操作數(shù)棧,創(chuàng)建新的幀frame b,并且該幀為當(dāng)前活動(dòng)幀,frame a為非活動(dòng)。局部變量為兩個(gè)參數(shù)的引用。程序計(jì)數(shù)器指向方法println。
圖.4 執(zhí)行完println方法
當(dāng)執(zhí)行完println方式時(shí),frame b棧彈出終結(jié),frame a重新成為當(dāng)前活動(dòng)幀,程序計(jì)數(shù)器從invokevirtual跳過(guò)指向return指令,操作數(shù)棧為空。heap中hello,world及out已經(jīng)沒(méi)有其他程序引用該對(duì)象,gc將會(huì)自動(dòng)回收。