Java虛擬機(jī)(JVM)是可運(yùn)行Java代碼的假想計(jì)算機(jī)。
創(chuàng)新互聯(lián)主要業(yè)務(wù)有網(wǎng)站營(yíng)銷策劃、成都網(wǎng)站制作、成都網(wǎng)站建設(shè)、微信公眾號(hào)開(kāi)發(fā)、成都微信小程序、成都h5網(wǎng)站建設(shè)、程序開(kāi)發(fā)等業(yè)務(wù)。一次合作終身朋友,是我們奉行的宗旨;我們不僅僅把客戶當(dāng)客戶,還把客戶視為我們的合作伙伴,在開(kāi)展業(yè)務(wù)的過(guò)程中,公司還積累了豐富的行業(yè)經(jīng)驗(yàn)、營(yíng)銷型網(wǎng)站資源和合作伙伴關(guān)系資源,并逐漸建立起規(guī)范的客戶服務(wù)和保障體系。
只要根據(jù)JVM規(guī)格描述將解釋器移植到特定的計(jì)算機(jī)上,就能保證經(jīng)過(guò)編譯的任何Java代碼能夠在該系統(tǒng)上運(yùn)行。
本文首先簡(jiǎn)要介紹纖悔碼從Java文件的編譯到最終執(zhí)行的過(guò)程,隨后對(duì)JVM規(guī)格描述作一說(shuō)明。
一.Java源文件的編譯、下載、解釋和執(zhí)行
Java應(yīng)用程序的開(kāi)發(fā)周期包括編譯、下載、解釋和執(zhí)行幾個(gè)部分。
Java編譯程序?qū)ava源程序翻譯為JVM可執(zhí)行代碼?字節(jié)碼。
這一編譯過(guò)程同C/C++的編譯有些不同。
當(dāng)C編譯器編譯生成一個(gè)對(duì)象的代碼時(shí),該代碼是為在某一特定硬件平臺(tái)運(yùn)行而產(chǎn)生的。
因此,在編譯過(guò)程中,編譯程序通過(guò)查表將所有對(duì)符號(hào)的引用轉(zhuǎn)換為特定的內(nèi)存偏移量,以保證程序運(yùn)行。
Java編譯器卻不將對(duì)變量和方法的引用編譯為數(shù)值引用,也不確定程序執(zhí)行過(guò)程中的內(nèi)存布局,而是將這些符號(hào)引用信息保留在字節(jié)碼中,由解釋器在運(yùn)行過(guò)程中創(chuàng)立內(nèi)存布局,然后再通過(guò)查表來(lái)確定一個(gè)方法所在的地址。
這樣就有效的保證了Java的可移植性和安全性。
運(yùn)行JVM字節(jié)碼的工作是由解釋器來(lái)完成的。
解釋執(zhí)行過(guò)程分三部進(jìn)行:代碼的裝入、代碼的校驗(yàn)和代碼的執(zhí)行。
裝入代碼的工作由"類裝載器"(classloader)完成。
類裝載器負(fù)責(zé)裝入運(yùn)行一個(gè)程序需要的所有代碼,這也包括程序代碼中的類所繼承的類和被其調(diào)用的類。
當(dāng)類裝載器裝入一個(gè)類時(shí),該類被放在自己的名字空間中。
除了通過(guò)符號(hào)引用自己名字空間以外的類,類之間沒(méi)有其他辦法可以影響其他類。
在本臺(tái)計(jì)算機(jī)上的所有類都在同一地址空間內(nèi),而所有從外部引進(jìn)的類,都有一個(gè)自己獨(dú)立的名字空間。
這使得本地類通過(guò)共享相同的名字空間獲得較高的運(yùn)行效率,同時(shí)又保證它們與從外部引進(jìn)的類不會(huì)相互影響。
當(dāng)裝入了運(yùn)行程序需要的所有類后,解釋器便可確定整個(gè)可執(zhí)行程序的內(nèi)存布局。
解釋器為符號(hào)引用同特定的地址空間建立對(duì)應(yīng)關(guān)系及查詢表。
通過(guò)在這一階段確定代碼的內(nèi)存布局,Java很好地解決了由超類改變而使子類崩潰的問(wèn)題,同時(shí)也防止了代碼對(duì)地址的非法訪問(wèn)。
隨后,被裝入的代碼由字節(jié)碼校驗(yàn)器進(jìn)行檢查。
校驗(yàn)器可發(fā)現(xiàn)操作數(shù)棧溢出,非法數(shù)據(jù)類型轉(zhuǎn)化等多種錯(cuò)誤。
通過(guò)校驗(yàn)后,代碼便開(kāi)始執(zhí)行了。
Java字節(jié)碼的執(zhí)行有兩種方式:
1.即時(shí)編譯方式:解釋器先將字節(jié)碼編譯成機(jī)器碼,然后再執(zhí)行該機(jī)器碼。
2.解釋執(zhí)行方式:解釋器通過(guò)每次解釋并執(zhí)行一小段代碼來(lái)完成Java字節(jié)碼程序的所有操作。
通常采用的是第二種方法。
由于JVM規(guī)格描述毀哪具有足夠的靈活性,這使得將字節(jié)碼翻譯為機(jī)器代碼的工作
具有較高的效率。
對(duì)于那些對(duì)運(yùn)行速度要求較高的應(yīng)用程序,解釋器可將Java字節(jié)碼即時(shí)編譯為機(jī)器碼,從而很好地保證了Java代碼的可移植性和高性能。
二.JVM規(guī)格描述
JVM的設(shè)計(jì)目標(biāo)是提供一個(gè)基于抽象規(guī)格描述的計(jì)算機(jī)模型,為解釋程序開(kāi)發(fā)人員提很好的靈活性,同時(shí)也確保Java代碼可在符合該規(guī)范的任何系統(tǒng)上運(yùn)行。
JVM對(duì)前散其實(shí)現(xiàn)的某些方面給出了具體的定義,特別是對(duì)Java可執(zhí)行代碼,即字節(jié)碼(Bytecode)的格式給出了明確的規(guī)格。
這一規(guī)格包括操作碼和操作數(shù)的語(yǔ)法和數(shù)值、標(biāo)識(shí)符的數(shù)值表示方式、以及Java類文件中的Java對(duì)象、常量緩沖池在JVM的存儲(chǔ)映象。
這些定義為JVM解釋器開(kāi)發(fā)人員提供了所需的信息和開(kāi)發(fā)環(huán)境。
Java的設(shè)計(jì)者希望給開(kāi)發(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)緊隨在操作碼的后面,其長(zhǎng)度根據(jù)需要而不同。
操作碼用于指定一條指令操作的性質(zhì)(在這里我們采用匯編符號(hào)的形式進(jìn)行說(shuō)明),如iload表示從存儲(chǔ)器中裝入一個(gè)整數(shù),anewarray表示為一個(gè)新數(shù)組分配空間,iand表示兩個(gè)整數(shù)的"與",ret用于流程控制,表示從對(duì)某一方法的調(diào)用中返回。
當(dāng)長(zhǎng)度大于8位時(shí),操作數(shù)被分為兩個(gè)以上字節(jié)存放。
JVM采用了"bigendian"的編碼方式來(lái)處理這種情況,即高位bits存放在低字節(jié)中。
這同Motorola及其他的RISCCPU采用的編碼方式是一致的,而與Intel采用的"littleendian"的編碼方式即低位bits存放在低位字節(jié)的方法不同。
Java指令系統(tǒng)是以Java語(yǔ)言的實(shí)現(xiàn)為目的設(shè)計(jì)的,其中包含了用于調(diào)用方法和監(jiān)視多先程系統(tǒng)的指令。
Java的8位操作碼的長(zhǎng)度使得JVM最多有256種指令,目前已使用了160多種操作碼。
2.2JVM指令系統(tǒng)
所有的CPU均包含用于保存系統(tǒng)狀態(tài)和處理器所需信息的寄存器組。
如果虛擬機(jī)定義較多的寄存器,便可以從中得到更多的信息而不必對(duì)?;騼?nèi)存進(jìn)行訪問(wèn),這有利于提高運(yùn)行速度。
然而,如果虛擬機(jī)中的寄存器比實(shí)際CPU的寄存器多,在實(shí)現(xiàn)虛擬機(jī)時(shí)就會(huì)占用處理器大量的時(shí)間來(lái)用常規(guī)存儲(chǔ)器模擬寄存器,這反而會(huì)降低虛擬機(jī)的效率。
針對(duì)這種情況,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è)??蚣馨ㄒ韵氯愋畔ⅲ?/p>
局部變量
執(zhí)行環(huán)境
操作數(shù)棧
局部變量用于存儲(chǔ)一個(gè)類的方法中所用到的局部變量。
vars寄存器指向該變量表中的第一個(gè)局部變量。
執(zhí)行環(huán)境用于保存解釋器對(duì)Java字節(jié)碼進(jìn)行解釋過(guò)程中所需的信息。
它們是:上次調(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ǔ)空間后,便開(kāi)始記錄對(duì)該實(shí)例所占用的內(nèi)存區(qū)域的使用。
一旦對(duì)象使用完畢,便將其回收到堆中。
在Java語(yǔ)言中,除了new語(yǔ)句外沒(méi)有其他方法為一對(duì)象申請(qǐng)和釋放內(nèi)存。
對(duì)內(nèi)存進(jìn)行釋放和回收的工作是由Java運(yùn)行系統(tǒng)承擔(dān)的。
這允許Java運(yùn)行系統(tǒng)的設(shè)計(jì)者自己決定碎片回收的方法。
在SUN公司開(kāi)發(fā)的Java解釋器和HotJava環(huán)境中,碎片回收用后臺(tái)線程的方式來(lá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é)碼。
對(duì)于這兩種存儲(chǔ)區(qū)域具體實(shí)現(xiàn)方式在JVM規(guī)格中沒(méi)有明確規(guī)定。
這使得Java應(yīng)用程序的存儲(chǔ)布局必須在運(yùn)行過(guò)程中確定,依賴于具體平臺(tái)的實(shí)現(xiàn)方式。
JVM是為Java字節(jié)碼定義的一種獨(dú)立于具體平臺(tái)的規(guī)格描述,是Java平 *** 立性的基礎(chǔ)。
目前的JVM還存在一些限制和不足,有待于進(jìn)一步的完善,但無(wú)論如何,JVM的思想是成功的。
對(duì)比分析:如果把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”不是通過(guò)硬件實(shí)現(xiàn)的,而是用軟件實(shí)現(xiàn)的。
Java解釋器實(shí)際上就是特定的平臺(tái)下的一個(gè)應(yīng)用程序。
只要實(shí)現(xiàn)了特定平臺(tái)下的解釋器程序,Java字節(jié)碼就能通過(guò)解釋器程序在該平臺(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編程開(kāi)發(fā)是目前市場(chǎng)上使用范圍非常廣泛的一種編程開(kāi)發(fā)語(yǔ)言。
今天我們就一起來(lái)了解一下,在java編程中關(guān)于字節(jié)碼的一些指令的編譯與執(zhí)行方法。
java文件編譯后的class文件,java跨平臺(tái)的中間層,JVM通過(guò)對(duì)字節(jié)碼的解釋執(zhí)行(執(zhí)行模式,還有JIT編譯執(zhí)行,下面講解),屏蔽對(duì)操作系統(tǒng)的依賴。
一個(gè)字節(jié)(8位)可以儲(chǔ)存256中不同的指令,這樣的指令就是字節(jié)碼,java所有指令有200個(gè)左右,這些指令組成了字節(jié)碼文件(.class)。
一、字節(jié)碼的主要指令:.class文件里面的十六進(jìn)制文件,其中CAFEBABE是標(biāo)志這個(gè)文件為java的編譯后的文件,00000034代表版本號(hào),01670700一個(gè)字節(jié)(8位)就是一個(gè)字節(jié)指令,由于數(shù)值指令太難看懂,我們可以用javap將指令翻譯為助記指令。
1、加載或儲(chǔ)存指令在棧幀中,通過(guò)指令操作數(shù)據(jù)在局部變量表與操作棧間傳遞。
ILOAD、ALOAD:將int、對(duì)象引用類型從局部變量表壓入操作棧頂;ISTORE、ASTORE:將int、對(duì)象引用類型從操作棧頂儲(chǔ)存到局部變量表里;ICONST、BIPUSH、SIPUSH、LDC:將族敗常亮加載到操作棧頂。
2、首行運(yùn)算指令對(duì)操作棧上的值進(jìn)行運(yùn)算,并把結(jié)果寫(xiě)入操作棧頂者穗嘩,如IADD、IMUL。
3、類型轉(zhuǎn)換指令I(lǐng)2L、D2F4、對(duì)象創(chuàng)建與訪問(wèn)指令NEW除了字節(jié)碼指令外,湖北電腦培訓(xùn)認(rèn)為還包括像LINENUMBER儲(chǔ)存字節(jié)碼與源碼對(duì)應(yīng),方便調(diào)試定位;LOCALVARIABLE儲(chǔ)存當(dāng)前方法使用到的局部表量表。
二、java源碼文件轉(zhuǎn)化為字節(jié)碼(.class)文件的過(guò)程JAVA源文件----------詞法解析----------語(yǔ)法解析----------語(yǔ)義分析---------生成字節(jié)碼---------字節(jié)碼文件詞法分析:根據(jù)空格分割出單詞、操作符等,形成token信息流;語(yǔ)法分析:根據(jù)token流和java語(yǔ)法規(guī)范生成語(yǔ)法樹(shù);語(yǔ)義分析:檢查關(guān)鍵字、類型匹配是否正確;
第一步(編譯): 創(chuàng)建完源文件之后,程序會(huì)先被編譯為.class文件。Java編譯一個(gè)類時(shí),如果這個(gè)類所依賴的類還沒(méi)有被編譯,編譯器就會(huì)先編譯這個(gè)被依賴的類,然后引用,否則直接引用,這個(gè)有點(diǎn)象make。
如果java編譯器在指定目錄下找不到該類所其依賴的類的.class文件或者.java源文件的話,編譯器話報(bào)“cant find symbol”的錯(cuò)誤。
第二步(運(yùn)行):java類運(yùn)行的過(guò)程大概可分為兩個(gè)過(guò)程:1、類的加載 2、類的執(zhí)行。需豎罩要說(shuō)明的是:JVM主要在程序第一次主動(dòng)使用類的時(shí)候,才會(huì)去加載該類。也就是說(shuō),JVM并不是在一開(kāi)始就把一個(gè)程序就所有的類都加載到內(nèi)存中,而是到不得不用的時(shí)候才把它加載進(jìn)來(lái),而且只加載一次。
特別說(shuō)明:java類中所有public和protected的實(shí)例方法都采用動(dòng)態(tài)綁定機(jī)制,所有私有方法、靜態(tài)方法、構(gòu)造器及初始化方法clinit都是采用靜態(tài)綁定機(jī)制。而使用動(dòng)態(tài)綁定機(jī)制的時(shí)候會(huì)用到方法表,靜態(tài)綁定時(shí)并不會(huì)用到。
擴(kuò)展資料:
Java整個(gè)編譯以及運(yùn)行的過(guò)程相當(dāng)繁瑣,本文通過(guò)一個(gè)簡(jiǎn)單的程序來(lái)簡(jiǎn)單的說(shuō)明整個(gè)流程。
Java代碼編譯:是由Java源碼編譯器來(lái)完成;
Java字節(jié)碼的執(zhí)行:是由JVM執(zhí)行引擎來(lái)完成
Java程序從源文件創(chuàng)建到程序運(yùn)行要經(jīng)過(guò)兩大步驟:
1、源文件由編譯器編譯成字節(jié)碼(ByteCode)跡纖旁
2、字節(jié)碼由java虛擬機(jī)解釋運(yùn)行。因?yàn)閖ava程序既要編譯同時(shí)也要經(jīng)過(guò)姿橡JVM的解釋運(yùn)行,所以說(shuō)Java被稱為半解釋語(yǔ)言( "semi-interpreted" language)。