深入理解Java之java虛擬機(jī)干凈利落的規(guī)范總結(jié) 上
- 作者:新網(wǎng)
- 來源:新網(wǎng)
- 瀏覽:100
- 2018-05-03 17:57:51
要去正確地實(shí)現(xiàn)一臺(tái)Java虛擬機(jī),就需要正確地讀取class文件中每一條字節(jié)碼指令并且能正確執(zhí)行這些指令所蘊(yùn)含的操作即可。
<
div> 要去正確地實(shí)現(xiàn)一臺(tái)Java
虛擬機(jī),就需要正確地讀取class文件中每一條字節(jié)碼指令并且能正確執(zhí)行這些指令所蘊(yùn)含的操作即可。
數(shù)據(jù)類型
和Java語(yǔ)言類似,在Java虛擬機(jī)中的數(shù)據(jù)類型也可以分為基本類型和引用類型兩種,所以也存在原始值和引用值兩種類型的數(shù)值。它們可用于變量賦值、參數(shù)傳遞、方法返回和運(yùn)算操作。
原始類型與值
Java虛擬機(jī)所支持的原始數(shù)據(jù)類型包括數(shù)值類型、boolean類型、和returnAddress類型
數(shù)值類型分為整數(shù)類型和浮點(diǎn)類型,分別是char,byte,short,int,long;浮點(diǎn)類型即float和double,這里和Java語(yǔ)言中的一致。
returnAddress翻譯過來是返回地址,其實(shí)returnAddress類型的值指向一條虛擬機(jī)指令的操作碼。它在虛擬機(jī)中比較典型的一個(gè)應(yīng)用場(chǎng)景是用于jsr程序段落跳轉(zhuǎn),在try-catch異常處理以及finally代碼塊經(jīng)常出現(xiàn)。和數(shù)值類的原生類型不同,returnAddress類型在Java語(yǔ)言之中并不存在相應(yīng)的類型,而且也無法在程序運(yùn)行期間修改。
雖然Java虛擬機(jī)定義了boolean這種數(shù)據(jù)類型,但是只對(duì)它提供了十分有限的支持。在Java虛擬機(jī)中并沒有任何供boolean值專用的字節(jié)碼指令,Java語(yǔ)言表達(dá)式所操作的boolean值,在編譯之后都使用Java虛擬機(jī)中的int數(shù)據(jù)類型來代替。(Java虛擬機(jī)會(huì)把boolean數(shù)組元素中的true采用1來表示,false采用0來表示,當(dāng)Java編譯器把Java語(yǔ)言中的boolean類型值映射為Java虛擬機(jī)的int類型值時(shí),也必須用上述表示方式)
引用類型與值
Java虛擬機(jī)中有三種引用類型:類類型、數(shù)組類型和接口類型。它們分別指向動(dòng)態(tài)創(chuàng)建的類實(shí)例、數(shù)組實(shí)例和某個(gè)接口的類實(shí)例或數(shù)組實(shí)例。
數(shù)組類型最外面那一維元素的類型叫做數(shù)組類型的組件類型。一個(gè)數(shù)組的組件類型也可以是數(shù)組。從任意一個(gè)數(shù)組開始,如果發(fā)現(xiàn)其組件類型也是數(shù)組類型,那就繼續(xù)取這個(gè)小數(shù)組的組件類型,不斷執(zhí)行這樣的操作,最終一定可以遇到組件類型不是數(shù)組的情況,這時(shí)就把這種類型成為本數(shù)組的元素類型。數(shù)組的元素類型必須是原生類型、類類型或者接口類型之一。
在引用類型的值中還有一個(gè)特殊的值:null,當(dāng)一個(gè)引用不指向任何對(duì)象的時(shí)候,它的值就用null來表示。一個(gè)為null的引用,起初并不具備任何實(shí)際的運(yùn)行期類型,但是它可轉(zhuǎn)型為任意的引用類型。引用類型的默認(rèn)值就是null。Java虛擬機(jī)規(guī)范并沒有規(guī)定null在虛擬機(jī)實(shí)現(xiàn)中應(yīng)當(dāng)怎樣用編碼來表示。
運(yùn)行時(shí)數(shù)據(jù)區(qū)域
棧幀:棧幀是用來存儲(chǔ)數(shù)據(jù)和部分過程結(jié)果的數(shù)據(jù)結(jié)構(gòu),同時(shí)也用來處理動(dòng)態(tài)鏈接、方法返回值和異常分派。棧幀又是存儲(chǔ)在棧中(包括Java虛擬機(jī)棧和本地方法棧),它隨著方法調(diào)用而創(chuàng)建,隨著方法結(jié)束而銷毀,其實(shí)也就是一個(gè)方法執(zhí)行的過程也對(duì)應(yīng)著棧幀的入棧和出棧的過程。無論方法是正常完成還是異常完成(拋出了在方法內(nèi)未被捕獲的異常)都算作方法結(jié)束。棧幀的存儲(chǔ)
空間由創(chuàng)建它的線程分配在Java虛擬機(jī)棧之中,每一個(gè)棧幀都有自己的本地變量表、操作數(shù)棧和指向當(dāng)前方法所屬的類的運(yùn)行時(shí)常量池的引用。
本地變量表和操作數(shù)棧的容量在編譯期確定,并通過相關(guān)方法的code屬性保存及提供給棧幀使用。因此,棧幀數(shù)據(jù)結(jié)構(gòu)的大小僅僅取決于Java虛擬機(jī)的實(shí)現(xiàn)。實(shí)現(xiàn)者可以在調(diào)用方法的時(shí)候給它們分配內(nèi)存。
在某條線程執(zhí)行的過程中的某個(gè)時(shí)間點(diǎn),只有目前正在執(zhí)行的那個(gè)方法的棧幀是活動(dòng)的。這個(gè)棧幀稱為當(dāng)前棧幀,這個(gè)棧幀對(duì)應(yīng)的方法稱為當(dāng)前方法,定義這個(gè)方法的類稱作當(dāng)前類。對(duì)局部變量表和操作數(shù)棧的各種操作,通常都是值對(duì)當(dāng)前棧幀的局部變量表和操作數(shù)棧所進(jìn)行的操作。
如果當(dāng)前方法調(diào)用了其他方法,或者當(dāng)前方法執(zhí)行結(jié)束,那這個(gè)方法的棧幀就不再是當(dāng)前棧幀了。調(diào)用新方法時(shí),新的棧幀也會(huì)隨之而創(chuàng)建,并且會(huì)隨著程序控制權(quán)移交到新方法而成為新的當(dāng)前棧幀。方法返回之際,當(dāng)前棧幀會(huì)傳回此方法給前一個(gè)棧幀,然后虛擬機(jī)會(huì)丟棄當(dāng)前棧幀,使得前一個(gè)棧幀重新成為當(dāng)前棧幀。
需要特別注意的是,棧幀是線程本地私有的數(shù)據(jù),不可能在一個(gè)棧幀之中引用另外一個(gè)線程的棧幀。
局部變量表
每個(gè)棧幀內(nèi)部都包含一組稱為局部變量表的變量列表。棧幀中局部變量表的長(zhǎng)度由編譯器決定,并卻存儲(chǔ)于類或接口的二進(jìn)制表示之中,即通過方法的code屬性保存及提供給棧幀使用。
一個(gè)局部變量可以保存一個(gè)類型為boolean、byte、char、short、int、float、reference或returnAddress的數(shù)據(jù)。兩個(gè)局部變量可以保存一個(gè)類型為long或double的數(shù)據(jù)。
局部變量使用索引來進(jìn)行定位訪問。首個(gè)局部變量的索引值為0。局部變量的索引值是個(gè)整數(shù),它大于等于0,且小于局部變量表的長(zhǎng)度。Java虛擬機(jī)使用局部變量表來完成方法調(diào)用時(shí)的參數(shù)傳遞。當(dāng)調(diào)用類方法時(shí),它的參數(shù)將會(huì)依次傳遞到局部變量表中從0開始的連續(xù)位置上。當(dāng)調(diào)用實(shí)例方法時(shí),第0個(gè)局部變量一定用來存儲(chǔ)該實(shí)例方法所在對(duì)象的引用(即Java語(yǔ)言中的this關(guān)鍵字)。后續(xù)其他參數(shù)將會(huì)傳遞至局部變量表中從1開始的連續(xù)位置上。
操作數(shù)棧:每個(gè)棧幀內(nèi)部都包含一個(gè)稱為操作數(shù)棧的后進(jìn)后出棧。棧幀中操作數(shù)棧的最大深度由編譯期決定,并且通過方法的code屬性保存及提供給棧幀使用。棧幀剛創(chuàng)建的時(shí)候操作數(shù)棧是空的。Java虛擬機(jī)提供一些字節(jié)碼指令來從局部變量表或者對(duì)象實(shí)例的字段中復(fù)制常量或變量值到操作數(shù)棧中,也提供了一些指令用于從操作數(shù)棧取走數(shù)據(jù)、操作數(shù)據(jù)以及把操作結(jié)果重新入棧。在調(diào)用方法時(shí),操作數(shù)棧也用來準(zhǔn)備調(diào)用方法的參數(shù)以及接收方法返回結(jié)果。例如iadd字節(jié)碼指令的作用是將兩個(gè)int類型的數(shù)值相加,它要求在執(zhí)行之前操作數(shù)棧的棧頂已經(jīng)存在兩個(gè)由前面的其他指令所放入的int類型數(shù)值。在執(zhí)行iadd指令時(shí),兩個(gè)int類型數(shù)值出棧,相加求和之后求和結(jié)果重新入棧。操作數(shù)棧的每個(gè)位置上可以保存一個(gè)Java虛擬機(jī)中定義的數(shù)據(jù)類型的值,包括long和double類型。在任意時(shí)刻,操作數(shù)棧都會(huì)有一個(gè)確定的棧深度,一個(gè)long或者double類型的數(shù)據(jù)會(huì)占用兩個(gè)單位的棧深度,其他數(shù)據(jù)類型則會(huì)占用一個(gè)單位的棧深度。
動(dòng)態(tài)鏈接
每個(gè)棧幀內(nèi)部都包含一個(gè)指向當(dāng)前方法所在類型的運(yùn)行時(shí)常量池的引用,以便對(duì)當(dāng)前方法的代碼實(shí)現(xiàn)動(dòng)態(tài)鏈接。在class文件里面一個(gè)方法若要調(diào)用其他方法,或者訪問成員變量,則需要通過符號(hào)引用來表示。動(dòng)態(tài)鏈接的作用就是將這些符號(hào)引用所表示的方法轉(zhuǎn)換為對(duì)實(shí)際方法的直接引用。類加載的過程中將要解析尚未被解析的符號(hào)引用,并且將對(duì)變量的訪問轉(zhuǎn)化為變量在程序運(yùn)行時(shí),位于存儲(chǔ)結(jié)構(gòu)中的正確偏移量。由于對(duì)其他類中的方法和變量進(jìn)行了晚期綁定,所以即便那些類發(fā)生變化,也不會(huì)影響調(diào)用它們的方法。
對(duì)象的表示
Java虛擬機(jī)規(guī)范不強(qiáng)制規(guī)定對(duì)象的內(nèi)部結(jié)構(gòu)應(yīng)該如何表示。在具體實(shí)現(xiàn)中,一般有兩種對(duì)象的訪問方式,分別是通過句柄訪問對(duì)象以及通過直接指針訪問對(duì)象。
在之前的博客里我也總結(jié)過這兩種對(duì)象訪問方式的優(yōu)劣,想要了解的同學(xué)可以參考這篇博客
特殊方法
在Java虛擬機(jī)層面,Java編程語(yǔ)言的構(gòu)造器是以一個(gè)名為的特殊實(shí)例初始方法的形式出現(xiàn)的。這個(gè)方法名稱是由編譯器命名的,因?yàn)樗⒎且粋€(gè)合法的Java方法名字,不可能通過程序編碼的方式實(shí)現(xiàn)。實(shí)例初始化方法的初始化期間,通過Java虛擬機(jī)的invokespecial指令來調(diào)用,而且只能在尚未初始化的實(shí)力上調(diào)用該指令。構(gòu)造器的訪問權(quán)限也會(huì)約束由該構(gòu)造器所衍生出來的實(shí)例初始化方法。
一個(gè)類或者接口最多可以包含不超過一個(gè)類或接口的初始化方法,類或接口就是通過這個(gè)方法完成初始化的。這個(gè)方法是一個(gè)不包含參數(shù)的、返回類型為void的方法,名為。
在class文件中把其他方法命名為是沒有意義的,這些方法并不是類或接口的初始化方法,它們既不能被字節(jié)碼指令調(diào)用,也不會(huì)被虛擬機(jī)自己調(diào)用。當(dāng)class文件的版本號(hào)不小于51.0時(shí),方法想要成為類或接口的初始化方法,必須設(shè)置ACC_STATIC標(biāo)志。
異常
Java虛擬機(jī)里面的異常使用Throwable或其子類的實(shí)例來表示,拋異常的本質(zhì)實(shí)際上是程序控制權(quán)轉(zhuǎn)移的一種即時(shí)的、非局部的轉(zhuǎn)換---從異常拋出的地方轉(zhuǎn)換至異常處理的地方。
絕大多數(shù)異常的產(chǎn)生都是由于當(dāng)前線程執(zhí)行的某個(gè)操作所導(dǎo)致的,這種可以稱為同步異常。與之相對(duì),異步異??梢栽诔绦驁?zhí)行過程中隨時(shí)發(fā)生。Java虛擬機(jī)中異常的出現(xiàn)總是由下面三種原因之一導(dǎo)致的:
athrow字節(jié)碼指令被執(zhí)行;
虛擬機(jī)同步檢測(cè)到程序發(fā)生了非正常的執(zhí)行情況,這時(shí)異常必將緊接著發(fā)生在非正常執(zhí)行情況的字節(jié)碼指令之后拋出,而不會(huì)在執(zhí)行程序的過程中隨時(shí)拋出。例如:程序所執(zhí)行的操作可能會(huì)引發(fā)異常---當(dāng)字節(jié)碼指令所蘊(yùn)含的操作違反了Java語(yǔ)言的語(yǔ)義,如訪問一個(gè)超出數(shù)組邊界范圍的元素,或者是當(dāng)程序在加載或者連接時(shí)出現(xiàn)錯(cuò)誤;還有一種異常是使用某些資源的時(shí)候產(chǎn)生資源限制,比如說使用了太多的內(nèi)存。
由于以下原因,導(dǎo)致了異步異常的發(fā)生: 調(diào)用了Thread或者ThreadGroup的stop方法;Java虛擬機(jī)實(shí)現(xiàn)發(fā)生了內(nèi)部錯(cuò)誤。
當(dāng)某個(gè)線程調(diào)用了stop方法時(shí),將會(huì)影響到其他線程,或者在特定線程組中的所有線程。因?yàn)閟top方法的執(zhí)行常常會(huì)導(dǎo)致出現(xiàn)數(shù)據(jù)不一致的情況,這時(shí)候其他線程中出現(xiàn)的異常就是異步異常,因?yàn)檫@些異??赡艹霈F(xiàn)在線程執(zhí)行過程中的任何位置。虛擬機(jī)的內(nèi)部錯(cuò)誤也被認(rèn)為是一種異步異常。
虛擬機(jī)錯(cuò)誤
InternalError:實(shí)現(xiàn)虛擬機(jī)的軟件錯(cuò)誤、底層
主機(jī)系統(tǒng)的軟件錯(cuò)誤及硬件錯(cuò)誤都會(huì)導(dǎo)致Java虛擬機(jī)出現(xiàn)內(nèi)部錯(cuò)誤,InternalError是一個(gè)異步異常,它可能出現(xiàn)在程序中的任何位置;
OutOfMemoryError:當(dāng)Java虛擬機(jī)實(shí)現(xiàn)耗盡了所有虛擬或物理內(nèi)存,并且內(nèi)存自動(dòng)管理子系統(tǒng)無法回收到創(chuàng)建新對(duì)象所需的足夠內(nèi)存空間時(shí),虛擬機(jī)將拋出OutOfMemoryError。
StackOverflowError:當(dāng)Java虛擬機(jī)實(shí)現(xiàn)耗盡了線程全部的??臻g時(shí),虛擬機(jī)將會(huì)拋出StackOverflowError。
UnKnownError:當(dāng)某種錯(cuò)誤或異常出現(xiàn),但虛擬機(jī)實(shí)現(xiàn)又無法確定它具體是哪種異?;蝈e(cuò)誤,將會(huì)拋出UnKnownError。
由于通常虛擬機(jī)會(huì)對(duì)代碼進(jìn)行優(yōu)化,例如指令重排。那么在異常發(fā)生的時(shí)候,有一些在異常出現(xiàn)位置之后的代碼可能已經(jīng)執(zhí)行了,那這些優(yōu)化過的代碼必須保證它們提前執(zhí)行所產(chǎn)生的影響對(duì)用戶程序來說是不可見的。