Java虛擬機(jī)運(yùn)行機(jī)制(轉(zhuǎn))
JVM(Java虛擬機(jī))一種用于計(jì)算設(shè)備的規(guī)范,可用不同的方式(軟件或硬件)加以實(shí)現(xiàn)。編譯虛擬機(jī)的指令集與編譯微處理器的指令集非常類似。Java虛擬機(jī)包括一套字節(jié)碼指令集、一組寄存器、一個(gè)棧、一個(gè)垃圾回收堆和一個(gè)存儲(chǔ)方法域。Java虛擬機(jī)(JVM)是可運(yùn)行Java代碼的假想計(jì)算機(jī)。只要根據(jù)JVM規(guī)格描述將解釋器移植到特定的計(jì)算機(jī)上,就能保證經(jīng)過編譯的任何Java代碼能夠在該系統(tǒng)上運(yùn)行。
Java虛擬機(jī)是一個(gè)想象中的機(jī)器,在實(shí)際的計(jì)算機(jī)上通過軟件模擬來實(shí)現(xiàn)。Java虛擬機(jī)有自己想象中的硬件,如處理器、堆棧、寄存器等,還具有相應(yīng)的指令系統(tǒng)。
一、Java虛擬機(jī)(JVM)概述
1.為什么要使用Java虛擬機(jī)
Java語言的一個(gè)非常重要的特點(diǎn)就是與平臺(tái)的無關(guān)性。而使用Java虛擬機(jī)是實(shí)現(xiàn)這一特點(diǎn)的關(guān)鍵。一般的高級(jí)語言如果要在不同的平臺(tái)上運(yùn)行,至少需要編譯成不同的目標(biāo)代碼。而引入Java語言虛擬機(jī)后,Java語言在不同平臺(tái)上運(yùn)行時(shí)不需要重新編譯。Java語言使用模式Java虛擬機(jī)屏蔽了與具體平臺(tái)相關(guān)的信息,使得Java語言編譯程序只需生成在Java虛擬機(jī)上運(yùn)行的目標(biāo)代碼(字節(jié)碼),就可以在多種平臺(tái)上不加修改地運(yùn)行。Java虛擬機(jī)在執(zhí)行字節(jié)碼時(shí),把字節(jié)碼解釋成具體平臺(tái)上的機(jī)器指令執(zhí)行。
2.誰需要了解Java虛擬機(jī)
Java虛擬機(jī)是Java語言底層實(shí)現(xiàn)的基礎(chǔ),對Java語言感興趣的人都應(yīng)對Java虛擬機(jī)有個(gè)大概的了解。這有助于理解Java語言的一些性質(zhì),也有助于使用Java語言。對于要在特定平臺(tái)上實(shí)現(xiàn)Java虛擬機(jī)的軟件人員,Java語言的編譯器作者以及要用硬件芯片實(shí)現(xiàn)Java虛擬機(jī)的人來說,則必須深刻理解Java虛擬機(jī)的規(guī)范。另外,如果你想擴(kuò)展Java語言,或是把其它語言編譯成Java語言的字節(jié)碼,你也需要深入地了解Java虛擬機(jī)。
3.Java虛擬機(jī)支持的數(shù)據(jù)類型
Java虛擬機(jī)支持Java語言的基本數(shù)據(jù)類型如下:
byte://1字節(jié)有符號(hào)整數(shù)的補(bǔ)碼
short://2字節(jié)有符號(hào)整數(shù)的補(bǔ)碼
int://4字節(jié)有符號(hào)整數(shù)的補(bǔ)碼
long://8字節(jié)有符號(hào)整數(shù)的補(bǔ)碼
float://4字節(jié)IEEE754單精度浮點(diǎn)數(shù)
double://8字節(jié)IEEE754雙精度浮點(diǎn)數(shù)
char://2字節(jié)無符號(hào)Unicode字符
幾乎所有的Java類型檢查都是在編譯時(shí)完成的。上面列出的原始數(shù)據(jù)類型的數(shù)據(jù)在Java執(zhí)行時(shí)不需要用硬件標(biāo)記。操作這些原始數(shù)據(jù)類型數(shù)據(jù)的字節(jié)碼(指令)本身就已經(jīng)指出了操作數(shù)的數(shù)據(jù)類型,例如iadd、ladd、fadd和dadd指令都是把兩個(gè)數(shù)相加,其操作數(shù)類型別是int、long、float和double。虛擬機(jī)沒有給boolean(布爾)類型設(shè)置單獨(dú)的指令。boolean型的數(shù)據(jù)是由integer指令,包括integer返回來處理的。boolean型的數(shù)組則是用byte數(shù)組來處理的。虛擬機(jī)使用IEEE754格式的浮點(diǎn)數(shù)。不支持IEEE格式的較舊的計(jì)算機(jī),在運(yùn)行Java數(shù)值計(jì)算程序時(shí),可能會(huì)非常慢。
虛擬機(jī)支持的其它數(shù)據(jù)類型包括:
object//對一個(gè)Javaobject(對象)的4字節(jié)引用
returnAddress//4字節(jié),用于jsr/ret/jsr-w/ret-w指令
注:Java數(shù)組被當(dāng)作object處理。
虛擬機(jī)的規(guī)范對于object內(nèi)部的結(jié)構(gòu)沒有任何特殊的要求。在Sun公司的實(shí)現(xiàn)中,對object的引用是一個(gè)句柄,其中包含一對指針:一個(gè)指針指向該object的方法表,另一個(gè)指向該object的數(shù)據(jù)。用Java虛擬機(jī)的字節(jié)碼表示的程序應(yīng)該遵守類型規(guī)定。Java虛擬機(jī)的實(shí)現(xiàn)應(yīng)拒絕執(zhí)行違反了類型規(guī)定的字節(jié)碼程序。Java虛擬機(jī)由于字節(jié)碼定義的限制似乎只能運(yùn)行于32位地址空間的機(jī)器上。但是可以創(chuàng)建一個(gè)Java虛擬機(jī),它自動(dòng)地把字節(jié)碼轉(zhuǎn)換成64位的形式。從Java虛擬機(jī)支持的數(shù)據(jù)類型可以看出,Java對數(shù)據(jù)類型的內(nèi)部格式進(jìn)行了嚴(yán)格規(guī)定,這樣使得各種Java虛擬機(jī)的實(shí)現(xiàn)對數(shù)據(jù)的解釋是相同的,從而保證了Java的與平臺(tái)無關(guān)性和可
移植性。
二、Java虛擬機(jī)JVM規(guī)格描述
JVM的設(shè)計(jì)目標(biāo)是提供一個(gè)基于抽象規(guī)格描述的計(jì)算機(jī)模型,為解釋程序開發(fā)人員提很好的靈活性,同時(shí)也確保Java代碼可在符合該規(guī)范的任何系統(tǒng)上運(yùn)行。JVM對其實(shí)現(xiàn)的某些方面給出了具體的定義,特別是對Java可執(zhí)行代碼,即字節(jié)碼(Bytecode)的格式給出了明確的規(guī)格。這一規(guī)格包括操作碼和操作數(shù)的語法和數(shù)值、標(biāo)識(shí)符的數(shù)值表示方式、以及Java類文件中的Java對象、常量緩沖池在JVM的存儲(chǔ)映象。這些定義為JVM解釋器開發(fā)人員提供了所需的信息和開發(fā)環(huán)境。Java的設(shè)計(jì)者希望給開發(fā)人員以隨心所欲使用Java的自由。 JVM定義了控制Java代碼解釋執(zhí)行和具體實(shí)現(xiàn)的五種規(guī)格,它們是:
*JVM指令系統(tǒng)
*JVM寄存器
*JVM棧結(jié)構(gòu)
*JVM碎片回收堆
*JVM存儲(chǔ)區(qū)
2.1JVM指令系統(tǒng)
JVM指令系統(tǒng)同其他計(jì)算機(jī)的指令系統(tǒng)極其相似。Java指令也是由 操作碼和操作數(shù)兩部分組成。操作碼為8位二進(jìn)制數(shù),操作數(shù)進(jìn)緊隨在操作碼的后面,其長度根據(jù)需要而不同。操作碼用于指定一條指令操作的性質(zhì)(在這里我們采用匯編符號(hào)的形式進(jìn)行說明),如iload表示從存儲(chǔ)器中裝入一個(gè)整數(shù),anewarray表示為一個(gè)新數(shù)組分配空間,iand表示兩個(gè)整數(shù)的"與",ret用于流程控制,表示從對某一方法的調(diào)用中返回。當(dāng)長度大于8位時(shí),操作數(shù)被分為兩個(gè)以上字節(jié)存放。JVM采用了"big endian"的編碼方式來處理這種情況,即高位bits存放在低字節(jié)中。這同 Motorola及其他的RISC CPU采用的編碼方式是一致的,而與Intel采用的"little endian "的編碼方式即低位bits存放在低位字節(jié)的方法不同。
Java指令系統(tǒng)是以Java語言的實(shí)現(xiàn)為目的設(shè)計(jì)的,其中包含了用于調(diào)用方法和監(jiān)視多先程系統(tǒng)的指令。Java的8位操作碼的長度使得JVM最多有256種指令,目前已使用了160多種操作碼。
2.2JVM指令系統(tǒng)
所有的CPU均包含用于保存系統(tǒng)狀態(tài)和處理器所需信息的寄存器組。如果虛擬機(jī)定義較多的寄存器,便可以從中得到更多的信息而不必對棧或內(nèi)存進(jìn)行訪問,這有利于提高運(yùn)行速度。然而,如果虛擬機(jī)中的寄存器比實(shí)際CPU的寄存器多,在實(shí)現(xiàn)虛擬機(jī)時(shí)就會(huì)占用處理器大量的時(shí)間來用常規(guī)存儲(chǔ)器模擬寄存器,這反而會(huì)降低虛擬機(jī)的效率。針對這種情況,JVM只設(shè)置了4個(gè)最為常用的寄存器。它們是:
pc程序計(jì)數(shù)器
optop操作數(shù)棧頂指針
frame當(dāng)前執(zhí)行環(huán)境指針
vars指向當(dāng)前執(zhí)行環(huán)境中第一個(gè)局部變量的指針
所有寄存器均為32位。pc用于記錄程序的執(zhí)行。optop,frame和vars用于記錄指向Java棧區(qū)的指針。
2.3JVM棧結(jié)構(gòu)
作為基于棧結(jié)構(gòu)的計(jì)算機(jī),Java棧是JVM存儲(chǔ)信息的主要方法。當(dāng)JVM得到一個(gè)Java字節(jié)碼應(yīng)用程序后,便為該代碼中一個(gè)類的每一個(gè)方法創(chuàng)建一個(gè)棧框架,以保存該方法的狀態(tài)信息。每個(gè)棧框架包括以下三類信息:
局部變量
執(zhí)行環(huán)境
操作數(shù)棧
局部變量用于存儲(chǔ)一個(gè)類的方法中所用到的局部變量。vars寄存器指向該變量表中的第一個(gè)局部變量。
執(zhí)行環(huán)境用于保存解釋器對Java字節(jié)碼進(jìn)行解釋過程中所需的信息。它們是:上次調(diào)用的方法、局部變量指針和操作數(shù)棧的棧頂和棧底指針。執(zhí)行環(huán)境是一個(gè)執(zhí)行一個(gè)方法的控制中心。例如:如果解釋器要執(zhí)行iadd(整數(shù)加法),首先要從frame寄存器中找到當(dāng)前執(zhí)行環(huán)境,而后便從執(zhí)行環(huán)境中找到操作數(shù)棧,從棧頂彈出兩個(gè)整數(shù)進(jìn)行加法運(yùn)算,最后將結(jié)果壓入棧頂。
操作數(shù)棧用于存儲(chǔ)運(yùn)算所需操作數(shù)及運(yùn)算的結(jié)果。
2.4JVM碎片回收堆
Java類的實(shí)例所需的存儲(chǔ)空間是在堆上分配的。解釋器具體承擔(dān)為類實(shí)例分配空間的工作。解釋器在為一個(gè)實(shí)例分配完存儲(chǔ)空間后,便開始記錄對該實(shí)例所占用的內(nèi)存區(qū)域的使用。一旦對象使用完畢,便將其回收到堆中。
在Java語言中,除了new語句外沒有其他方法為一對象申請和釋放內(nèi)存。對內(nèi)存進(jìn)行釋放和回收的工作是由Java運(yùn)行系統(tǒng)承擔(dān)的。這允許Java運(yùn)行系統(tǒng)的設(shè)計(jì)者自己決定碎片回收的方法。在SUN公司開發(fā)的Java解釋器和Hot Java環(huán)境中,碎片回收用后臺(tái)線程的方式來執(zhí)行。這不但為運(yùn)行系統(tǒng)提供了良好的性能,而且使程序設(shè)計(jì)人員擺脫了自己控制內(nèi)存使用的風(fēng)險(xiǎn)。
2.5JVM存儲(chǔ)區(qū)
JVM有兩類存儲(chǔ)區(qū):常量緩沖池和方法區(qū)。常量緩沖池用于存儲(chǔ)類名稱、方法和字段名稱以及串常量。方法區(qū)則用于存儲(chǔ)Java方法的字節(jié)碼。對于這兩種存儲(chǔ)區(qū)域具體實(shí)現(xiàn)方式在JVM規(guī)格中沒有明確規(guī)定。這使得Java應(yīng)用程序的存儲(chǔ)布局必須在運(yùn)行過程中確定,依賴于具體平臺(tái)的實(shí)現(xiàn)方式。
JVM是為Java字節(jié)碼定義的一種獨(dú)立于具體平臺(tái)的規(guī)格描述,是Java平臺(tái)獨(dú)立性的基礎(chǔ)。目前的JVM還存在一些限制和不足,有待于進(jìn)一步的完善,但無論如何,JVM的思想是成功的。
對比分析:如果把Java原程序想象成我們的C++原程序,Java原程序編譯后生成的字節(jié)碼就相當(dāng)于C++原程序編譯后的80x86的機(jī)器碼(二進(jìn)制程序文件),JVM虛擬機(jī)相當(dāng)于80x86計(jì)算機(jī)系統(tǒng),Java解釋器相當(dāng)于80x86CPU。在80x86CPU上運(yùn)行的是機(jī)器碼,在Java解釋器上運(yùn)行的是Java字節(jié)碼。
Java解釋器相當(dāng)于運(yùn)行Java字節(jié)碼的“CPU”,但該“CPU”不是通過硬件實(shí)現(xiàn)的,而是用軟件實(shí)現(xiàn)的。Java解釋器實(shí)際上就是特定的平臺(tái)下的一個(gè)應(yīng)用程序。只要實(shí)現(xiàn)了特定平臺(tái)下的解釋器程序,Java字節(jié)碼就能通過解釋器程序在該平臺(tái)下運(yùn)行,這是Java跨平臺(tái)的根本。當(dāng)前,并不是在所有的平臺(tái)下都有相應(yīng)Java解釋器程序,這也是Java并不能在所有的平臺(tái)下都能運(yùn)行的原因,它只能在已實(shí)現(xiàn)了Java解釋器程序的平臺(tái)下運(yùn)行。
三、Java虛擬機(jī)JVM的體系結(jié)構(gòu)
剛才已經(jīng)提到,JVM可以由不同的廠商來實(shí)現(xiàn)。由于廠商的不同必然導(dǎo)致JVM在實(shí)現(xiàn)上的一些不同,然而JVM還是可以實(shí)現(xiàn)跨平臺(tái)的特性,這就要?dú)w功于設(shè)計(jì)JVM時(shí)的體系結(jié)構(gòu)了。
我們知道,一個(gè)JVM實(shí)例的行為不光是它自己的事,還涉及到它的子系統(tǒng)、存儲(chǔ)區(qū)域、數(shù)據(jù)類型和指令這些部分,它們描述了JVM的一個(gè)抽象的內(nèi)部體系結(jié)構(gòu),其目的不光規(guī)定實(shí)現(xiàn)JVM時(shí)它內(nèi)部的體系結(jié)構(gòu),更重要的是提供了一種方式,用于嚴(yán)格定義實(shí)現(xiàn)時(shí)的外部行為。每個(gè)JVM都有兩種機(jī)制,一個(gè)是裝載具有合適名稱的類(類或是接口),叫做類裝載子系統(tǒng);另外的一個(gè)負(fù)責(zé)執(zhí)行包含在已裝載的類或接口中的指令,叫做運(yùn)行引擎。每個(gè)JVM又包括方法區(qū)、堆、Java棧、程序計(jì)數(shù)器和本地方法棧這五個(gè)部分,這幾個(gè)部分和類裝載機(jī)制與運(yùn)行引擎機(jī)制一起組成的體系結(jié)構(gòu)圖為:
圖3JVM的體系結(jié)構(gòu)
JVM的每個(gè)實(shí)例都有一個(gè)它自己的方法域和一個(gè)堆,運(yùn)行于JVM內(nèi)的所有的線程都共享這些區(qū)域;當(dāng)虛擬機(jī)裝載類文件的時(shí)候,它解析其中的二進(jìn)制數(shù)據(jù)所包含的類信息,并把它們放到方法域中;當(dāng)程序運(yùn)行的時(shí)候,JVM把程序初始化的所有對象置于堆上;而每個(gè)線程創(chuàng)建的時(shí)候,都會(huì)擁有自己的程序計(jì)數(shù)器和Java棧,其中程序計(jì)數(shù)器中的值指向下一條即將被執(zhí)行的指令,線程的Java棧則存儲(chǔ)為該線程調(diào)用Java方法的狀態(tài);本地方法調(diào)用的狀態(tài)被存儲(chǔ)在本地方法棧,該方法棧依賴于具體的實(shí)現(xiàn)。
下面分別對這幾個(gè)部分進(jìn)行說明。
執(zhí)行引擎處于JVM的核心位置,在Java虛擬機(jī)規(guī)范中,它的行為是由指令集所決定的。盡管對于每條指令,規(guī)范很詳細(xì)地說明了當(dāng)JVM執(zhí)行字節(jié)碼遇到指令時(shí),它的實(shí)現(xiàn)應(yīng)該做什么,但對于怎么做卻言之甚少。Java虛擬機(jī)支持大約248個(gè)字節(jié)碼。每個(gè)字節(jié)碼執(zhí)行一種基本的CPU運(yùn)算,例如,把一個(gè)整數(shù)加到寄存器,子程序轉(zhuǎn)移等。Java指令集相當(dāng)于Java程序的匯編語言。
Java指令集中的指令包含一個(gè)單字節(jié)的操作符,用于指定要執(zhí)行的操作,還有0個(gè)或多個(gè)操作數(shù),提供操作所需的參數(shù)或數(shù)據(jù)。許多指令沒有操作數(shù),僅由一個(gè)單字節(jié)的操作符構(gòu)成。
虛擬機(jī)的內(nèi)層循環(huán)的執(zhí)行過程如下:
do{
取一個(gè)操作符字節(jié);
根據(jù)操作符的值執(zhí)行一個(gè)動(dòng)作;
}while(程序未結(jié)束)
由于指令系統(tǒng)的簡單性,使得虛擬機(jī)執(zhí)行的過程十分簡單,從而有利于提高執(zhí)行的效率。指令中操作數(shù)的數(shù)量和大小是由操作符決定的。如果操作數(shù)比一個(gè)字節(jié)大,那么它存儲(chǔ)的順序是高位字節(jié)優(yōu)先。例如,一個(gè)16位的參數(shù)存放時(shí)占用兩個(gè)字節(jié),其值為:
第一個(gè)字節(jié)*256+第二個(gè)字節(jié)字節(jié)碼。
指令流一般只是字節(jié)對齊的。指令tableswitch和lookup是例外,在這兩條指令內(nèi)部要求強(qiáng)制的4字節(jié)邊界對齊。
對于本地方法接口,實(shí)現(xiàn)JVM并不要求一定要有它的支持,甚至可以完全沒有。Sun公司實(shí)現(xiàn)Java本地接口(JNI)是出于可移植性的考慮,當(dāng)然我們也可以設(shè)計(jì)出其它的本地接口來代替Sun公司的JNI。但是這些設(shè)計(jì)與實(shí)現(xiàn)是比較復(fù)雜的事情,需要確保垃圾回收器不會(huì)將那些正在被本地方法調(diào)用的對象釋放掉。
Java的堆是一個(gè)運(yùn)行時(shí)數(shù)據(jù)區(qū),類的實(shí)例(對象)從中分配空間,它的管理是由垃圾回收來負(fù)責(zé)的:不給程序員顯式釋放對象的能力。Java不規(guī)定具體使用的垃圾回收算法,可以根據(jù)系統(tǒng)的需求使用各種各樣的算法。
Java方法區(qū)與傳統(tǒng)語言中的編譯后代碼或是Unix進(jìn)程中的正文段類似。它保存方法代碼(編譯后的java代碼)和符號(hào)表。在當(dāng)前的Java實(shí)現(xiàn)中,方法代碼不包括在垃圾回收堆中,但計(jì)劃在將來的版本中實(shí)現(xiàn)。每個(gè)類文件包含了一個(gè)Java類或一個(gè)Java界面的編譯后的代碼。可以說類文件是Java語言的執(zhí)行代碼文件。為了保證類文件的平臺(tái)無關(guān)性,Java虛擬機(jī)規(guī)范中對類文件的格式也作了詳細(xì)的說明。其具體細(xì)節(jié)請參考Sun公司的Java虛擬機(jī)規(guī)范。
Java虛擬機(jī)的寄存器用于保存機(jī)器的運(yùn)行狀態(tài),與微處理器中的某些專用寄存器類似。Java虛擬機(jī)的寄存器有四種:
pc: Java程序計(jì)數(shù)器;
optop: 指向操作數(shù)棧頂端的指針;
frame: 指向當(dāng)前執(zhí)行方法的執(zhí)行環(huán)境的指針;。
vars: 指向當(dāng)前執(zhí)行方法的局部變量區(qū)第一個(gè)變量的指針。
在上述體系結(jié)構(gòu)圖中,我們所說的是第一種,即程序計(jì)數(shù)器,每個(gè)線程一旦被創(chuàng)建就擁有了自己的程序計(jì)數(shù)器。當(dāng)線程執(zhí)行Java方法的時(shí)候,它包含該線程正在被執(zhí)行的指令的地址。但是若線程執(zhí)行的是一個(gè)本地的方法,那么程序計(jì)數(shù)器的值就不會(huì)被定義。
Java虛擬機(jī)的棧有三個(gè)區(qū)域:局部變量區(qū)、運(yùn)行環(huán)境區(qū)、操作數(shù)區(qū)。
局部變量區(qū)
每個(gè)Java方法使用一個(gè)固定大小的局部變量集。它們按照與vars寄存器的字偏移量來尋址。局部變量都是32位的。長整數(shù)和雙精度浮點(diǎn)數(shù)占據(jù)了兩個(gè)局部變量的空間,卻按照第一個(gè)局部變量的索引來尋址。(例如,一個(gè)具有索引n的局部變量,如果是一個(gè)雙精度浮點(diǎn)數(shù),那么它實(shí)際占據(jù)了索引n和n+1所代表的存儲(chǔ)空間)虛擬機(jī)規(guī)范并不要求在局部變量中的64位的值是64位對齊的。虛擬機(jī)提供了把局部變量中的值裝載到操作數(shù)棧的指令,也提供了把操作數(shù)棧中的值寫入局部變量的指令。
運(yùn)行環(huán)境區(qū)
在運(yùn)行環(huán)境中包含的信息用于動(dòng)態(tài)鏈接,正常的方法返回以及異常捕捉。
動(dòng)態(tài)鏈接
運(yùn)行環(huán)境包括對指向當(dāng)前類和當(dāng)前方法的解釋器符號(hào)表的指針,用于支持方法代碼的動(dòng)態(tài)鏈接。方法的class文件代碼在引用要調(diào)用的方法和要訪問的變量時(shí)使用符號(hào)。動(dòng)態(tài)鏈接把符號(hào)形式的方法調(diào)用翻譯成實(shí)際方法調(diào)用,裝載必要的類以解釋還沒有定義的符號(hào),并把變量訪問翻譯成與這些變量運(yùn)行時(shí)的存儲(chǔ)結(jié)構(gòu)相應(yīng)的偏移地址。動(dòng)態(tài)鏈接方法和變量使得方法中使用的其它類的變化不會(huì)影響到本程序的代碼。
正常的方法返回
如果當(dāng)前方法正常地結(jié)束了,在執(zhí)行了一條具有正確類型的返回指令時(shí),調(diào)用的方法會(huì)得到一個(gè)返回值。執(zhí)行環(huán)境在正常返回的情況下用于恢復(fù)調(diào)用者的寄存器,并把調(diào)用者的程序計(jì)數(shù)器增加一個(gè)恰當(dāng)?shù)臄?shù)值,以跳過已執(zhí)行過的方法調(diào)用指令,然后在調(diào)用者的執(zhí)行環(huán)境中繼續(xù)執(zhí)行下去。
異常捕捉
異常情況在Java中被稱作Error(錯(cuò)誤)或Exception(異常),是Throwable類的子類,在程序中的原因是:①動(dòng)態(tài)鏈接錯(cuò),如無法找到所需的class文件。②運(yùn)行時(shí)錯(cuò),如對一個(gè)空指針的引用。程序使用了throw語句。
當(dāng)異常發(fā)生時(shí),Java虛擬機(jī)采取如下措施:
§ 檢查與當(dāng)前方法相聯(lián)系的catch子句表。每個(gè)catch子句包含其有效指令范圍,能夠處理的異常類型,以及處理異常的代碼塊地址。
§ 與異常相匹配的catch子句應(yīng)該符合下面的條件:造成異常的指令在其指令范圍之內(nèi),發(fā)生的異常類型是其能處理的異常類型的子類型。如果找到了匹配的catch子句,那么系統(tǒng)轉(zhuǎn)移到指定的異常處理塊處執(zhí)行;如果沒有找到異常處理塊,重復(fù)尋找匹配的catch子句的過程,直到當(dāng)前方法的所有嵌套的catch子句都被檢查過。
§ 由于虛擬機(jī)從第一個(gè)匹配的catch子句處繼續(xù)執(zhí)行,所以catch子句表中的順序是很重要的。因?yàn)镴ava代碼是結(jié)構(gòu)化的,因此總可以把某個(gè)方法的所有的異常處理器都按序排列到一個(gè)表中,對任意可能的程序計(jì)數(shù)器的值,都可以用線性的順序找到合適的異常處理塊,以處理在該程序計(jì)數(shù)器值下發(fā)生的異常情況。
§ 如果找不到匹配的catch子句,那么當(dāng)前方法得到一個(gè)"未截獲異常"的結(jié)果并返回到當(dāng)前方法的調(diào)用者,好像異常剛剛在其調(diào)用者中發(fā)生一樣。如果在調(diào)用者中仍然沒有找到相應(yīng)的異常處理塊,那么這種錯(cuò)誤將被傳播下去。如果錯(cuò)誤被傳播到最頂層,那么系統(tǒng)將調(diào)用一個(gè)缺省的異常處理塊。
操作數(shù)棧區(qū)
機(jī)器指令只從操作數(shù)棧中取操作數(shù),對它們進(jìn)行操作,并把結(jié)果返回到棧中。選擇棧結(jié)構(gòu)的原因是:在只有少量寄存器或非通用寄存器的機(jī)器(如Intel486)上,也能夠高效地模擬虛擬機(jī)的行為。操作數(shù)棧是32位的。它用于給方法傳遞參數(shù),并從方法接收結(jié)果,也用于支持操作的參數(shù),并保存操作的結(jié)果。例如,iadd指令將兩個(gè)整數(shù)相加。相加的兩個(gè)整數(shù)應(yīng)該是操作數(shù)棧頂?shù)膬蓚€(gè)字。這兩個(gè)字是由先前的指令壓進(jìn)堆棧的。這兩個(gè)整數(shù)將從堆棧彈出、相加,并把結(jié)果壓回到操作數(shù)棧中。
每個(gè)原始數(shù)據(jù)類型都有專門的指令對它們進(jìn)行必須的操作。每個(gè)操作數(shù)在棧中需要一個(gè)存儲(chǔ)位置,除了long和double型,它們需要兩個(gè)位置。操作數(shù)只能被適用于其類型的操作符所操作。例如,壓入兩個(gè)int類型的數(shù),如果把它們當(dāng)作是一個(gè)long類型的數(shù)則是非法的。在Sun的虛擬機(jī)實(shí)現(xiàn)中,這個(gè)限制由字節(jié)碼驗(yàn)證器強(qiáng)制實(shí)行。但是,有少數(shù)操作(操作符dupe和swap),用于對運(yùn)行時(shí)數(shù)據(jù)區(qū)進(jìn)行操作時(shí)是不考慮類型的。
本地方法棧,當(dāng)一個(gè)線程調(diào)用本地方法時(shí),它就不再受到虛擬機(jī)關(guān)于結(jié)構(gòu)和安全限制方面的約束,它既可以訪問虛擬機(jī)的運(yùn)行期數(shù)據(jù)區(qū),也可以使用本地處理器以及任何類型的棧。例如,本地棧是一個(gè)C語言的棧,那么當(dāng)C程序調(diào)用C函數(shù)時(shí),函數(shù)的參數(shù)以某種順序被壓入棧,結(jié)果則返回給調(diào)用函數(shù)。在實(shí)現(xiàn)Java虛擬機(jī)時(shí),本地方法接口使用的是C語言的模型棧,那么它的本地方法棧的調(diào)度與使用則完全與C語言的棧相同。
四、Java虛擬機(jī)JVM的運(yùn)行過程
上面對虛擬機(jī)的各個(gè)部分進(jìn)行了比較詳細(xì)的說明,下面通過一個(gè)具體的例子來分析它的運(yùn)行過程。
虛擬機(jī)通過調(diào)用某個(gè)指定類的方法main啟動(dòng),傳遞給main一個(gè)字符串?dāng)?shù)組參數(shù),使指定的類被裝載,同時(shí)鏈接該類所使用的其它的類型,并且初始化它們。例如對于程序:
class HelloApp
{
public static void main(String[] args)
{
System.out.println("Hello World!");
for (int i = 0; i < args.length; i++ )
{
System.out.println(args);
}
}
}
編譯后在命令行模式下鍵入: java HelloApp run virtual machine
將通過調(diào)用HelloApp的方法main來啟動(dòng)java虛擬機(jī),傳遞給main一個(gè)包含三個(gè)字符串"run"、"virtual"、"machine"的數(shù)組。現(xiàn)在我們略述虛擬機(jī)在執(zhí)行HelloApp時(shí)可能采取的步驟。
開始試圖執(zhí)行類HelloApp的main方法,發(fā)現(xiàn)該類并沒有被裝載,也就是說虛擬機(jī)當(dāng)前不包含該類的二進(jìn)制代表,于是虛擬機(jī)使用ClassLoader試圖尋找這樣的二進(jìn)制代表。如果這個(gè)進(jìn)程失敗,則拋出一個(gè)異常。類被裝載后同時(shí)在main方法被調(diào)用之前,必須對類HelloApp與其它類型進(jìn)行鏈接然后初始化。鏈接包含三個(gè)階段:檢驗(yàn),準(zhǔn)備和解析。檢驗(yàn)檢查被裝載的主類的符號(hào)和語義,準(zhǔn)備則創(chuàng)建類或接口的靜態(tài)域以及把這些域初始化為標(biāo)準(zhǔn)的默認(rèn)值,解析負(fù)責(zé)檢查主類對其它類或接口的符號(hào)引用,在這一步它是可選的。類的初始化是對類中聲明的靜態(tài)初始化函數(shù)和靜態(tài)域的初始化構(gòu)造方法的執(zhí)行。一個(gè)類在初始化之前它的父類必須被初始化。整個(gè)過程如下:
圖4:JAVA虛擬機(jī)的運(yùn)行過程
posted on 2007-05-24 16:46 liaojiyong 閱讀(4886) 評(píng)論(0) 編輯 收藏 所屬分類: Java